算法分析(2):归并排序——发现了一个wiki上的BUG

  归并排序的核心思想是将已经排好序的A[p~q],和A[q~r](其中A为数组,p、q、r为数组下标)合并为单一已排序的数组A[p~r]。实现的代码如下:

void _Merge(int* input,int* output, int low, int mid, int high)
{
    int i = low;
    int j = mid;
    while (i < mid && j < high)
    {
        output[low++] = input[i] <= input[j] ? input[i++] : input[j++];
    }
    copy(&input[i], &input[mid], &output[low]);
    copy(&input[j], &input[high], &output[low]);
}

  但是想要排列一个数组,还需要将数组分解。我采用的是在wiki上面的方法,但是发现有错误。先上我经过修改的代码:

void Merge(int* a, int n)
{
    int *copy_a = a;
    int *b = new int[n];
    int start = 0;
    for (int seg = 1; seg < n; seg += seg)
    {
        for (start = 0; start <= n - seg; start += 2*seg)
        {
            _Merge_ascending(a, b, start, start + seg, min(start + 2 * seg, n));
        }
        if (start < n)
        {
            copy(&a[start], &a[n], &b[start]);
        }
        swap(a, b);
    }
    if (a != copy_a) {
        copy(a, &a[n], b);
        b = a;
    }
    delete[] b;
}

  上面的代码完成的过程为:先将所有元素两两合并,输入到数组b,然后a,b互换;在四四合并,并以此类推,直到所有元素被排序。

  下面上,wiki的代码:

void merge_sort(int arr[], int len) {
    int* a = arr;
    int* b = new int[len];
    for (int seg = 1; seg < len; seg += seg) {
        for (int start = 0; start + seg <= len; start += seg + seg)
            _merge_sort(a, b, start, start + seg, min(start + seg + seg, len));
        swap(a, b);
    }
    if (a != arr) {
        copy(a, &a[len], b);
        b = a;
    }
    delete[] b;
}

  可以看到,区别在完成一轮的合并后,wiki的代码没有将没有进行合并的元素输出到b数组。

  为此,我还测试了wiki的代码。测试方法为:数组元素个数从1~1000变化,每个元素使用随机数。每次将上节的插入排序和wiki的代码作比较,如果不同则记录下此次的数组长度。

    vector<clock_t> dt1;
    vector<clock_t> dt2;
    vector<int> seq;
    vector<int> error_count;
    vector<int> error_wiki;
    for (int i = 1; i < MAX; i++)
    {
        seq.clear();
        for (int j = 0; j < i; j++)
        {
            seq.push_back(rand()%1000);
        }
        //Show_array(arr, i);
        int *arr1 = new int[i];
        int *arr2 = new int[i];
        int *arr3 = new int[i];
        V2A(seq, arr1);
        V2A(seq, arr2);
        V2A(seq, arr3);

        //ascending
        auto t1 = GetCycleCount();
        Insert_ascending(arr1, i);
        auto t2 = GetCycleCount();
        Merge_ascending(arr2, i);
        auto t3 = GetCycleCount();
        Merge_ascending_wiki(arr3, i);

        //Compare
        if (!Compare_array(arr1, arr2, i))
            error_count.push_back(i);
        else
            if (!Compare_array(arr1, arr3, i))
                error_wiki.push_back(i);

        //decending
        auto t4 = GetCycleCount();
        Insert_decending(arr1, i);
        auto t5 = GetCycleCount();
        Merge_decending(arr2, i);
        auto t6 = GetCycleCount();
        dt1.push_back(((t2 - t1)+(t5-t4))/2);
        dt2.push_back(((t3 - t2)+(t6-t5))/2);
        Merge_decending_wiki(arr3, i);

        //Compare
        if (!Compare_array(arr1, arr2, i))
            error_count.push_back(-i);
        else
            if (!Compare_array(arr1, arr3, i))
                error_wiki.push_back(-i);

    }
    cout << "Error Count:" << error_count.size() << endl;
    for (auto &it : error_count)
        cout << it << "\t";
    cout << endl;

    cout << "Wiki Error Count:" << error_wiki.size() << endl;
    for (auto &it : error_wiki)
        cout << it << "\t";
    cout << endl;

  上面的代码还增加了计算插入排序和归并排序在不同元素个数下排序所用时间的比较。获取CPU时间的函数为:

inline unsigned __int64 GetCycleCount()
{
    __asm _emit 0x0F
    __asm _emit 0x31
}

  比较结果为

时间: 2024-10-11 22:35:42

算法分析(2):归并排序——发现了一个wiki上的BUG的相关文章

写一个Windows上的守护进程(1)开篇

写一个Windows上的守护进程(1)开篇 最近由于工作需要,要写一个守护进程,主要就是要在被守护进程挂了的时候再把它启起来.说起来这个功能是比较简单的,但是我前一阵子写了好多现在回头看起来比较糟糕的代码,所以就想这次写的顺眼一点.写完后发现,诶,还可以哟.于是就总结总结. 一.大致需求 1. 功能——当被守护进程挂掉后再次启动它 2. 可配置需要守护的进程 二.通盘考虑 1. 为了避免重复造轮子,况且有的轮子可能自己也造不出来,上boost库 2. 为了能够获得较高的权限和能够开机自动启动,将

写一个Windows上的守护进程(4)日志其余

写一个Windows上的守护进程(4)日志其余 这次把和日志相关的其他东西一并说了. 一.vaformat C++日志接口通常有两种形式:流输入形式,printf形式. 我采用printf形式,因为流输入不好控制格式. printf形式要求日志接口支持不定长参数,我没有直接在日志实现类里边支持不定长参数,而是只接受一个字符串参数,可以参见第一篇. 为什么呢? 如果要成为不定长参数,就是这样 bool log_string(const LOG_LEVEL level, const char* fi

XA中使用dblink发现的一个oracle bug

最近监控系统日志时发现出现一个新的错误信息,很奇怪,之前没有过.如下: ORA-24777:不允许使用不可移植的数据库链路. 跟踪程序进去发现是通过dblink查询某个远程库表,而且由于使用了XA分布式事务数据库连接.大家也许都知道XA有不少限制条件,之前也遇到过,都绕着走了.但是最近没有调整过程序也没有改变过dblink的配置,怎么回事呢?只能网上搜索,结果有人遇到过,说是oracle dblink改成shared就行了.个人觉得这个说法靠谱.于是现在测试系统上试了一下,果然有效. 可以为什么

腾讯舆情团队谈:如何发现下一个现象级游戏?

轰轰烈烈的 ChinaJoy 刚结束,各大游戏厂商又投入了新一轮产品研发和优化.回顾ChinaJoy,最受玩家追捧的当属目前最火爆的现象级游戏 IP,如<魔兽世界>.<火影忍者手游>.<冒险岛 2>等,上海 40 度的高温天也阻止不了玩家前往现场"朝圣"一把这些影响着我们生活的明星游戏.进入 2016 下半年,随着行业热捧的 VR 和电竞"元年"过半,人们正在更理性地关心:未来的"下一个"现象级游戏 IP 将从

写一个Windows上的守护进程(8)获取进程路径

写一个Windows上的守护进程(8)获取进程路径 要想守护某个进程,就先得知道这个进程在不在.我们假设要守护的进程只会存在一个实例(这也是绝大部分情形). 我是遍历系统上的所有进程,然后判断他们的路径和要守护的进程是否一致,以此来确定进程是否存在. 遍历进程大家都知道用CreateToolhelp32Snapshot系列API,但是他们最后取得的是进程exe名称,不是全路径,如果仅依靠名称就可以达到目的也就罢了,但是有的时候还是得取到全路径,这样会更靠谱一些. 那么问题来了,如何取到进程全路径

写一个Windows上的守护进程(7)捕获异常并生成dump

写一个Windows上的守护进程(7)捕获异常并生成dump 谁都不能保证自己的代码不出bug.一旦出了bug,最好是崩溃掉,这样很快就能被发现,若是不崩溃,只是业务处理错了,就麻烦了,可能很长时间之后才能被发现. 那么如果崩溃掉,怎么查错呢? 写过Windows驱动的同学应该知道,一旦崩溃,系统会生成dump文件,然后就可以根据dump文件.pdb文件.源码用windbg分析了.应用层的程序同样可以在崩溃的时候生成dump文件,只是没人帮你完成这个步骤,得自己动手. 1. API 这里涉及到的

没事写个幻灯片玩一下,发现了一个问题

没事写个幻灯片玩了一下,开始没有问题,挺好的,后来我把页面最小化了,然后再最大化之后,整个效果就错了,应该是id为ppt的滚动距离错了,不知道怎么回事,有人碰见过类似的问题吗? <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>幻灯片</title> <style type="text/css"> *{ mar

今天发现了一个快捷方式(swing开发中的程序focus点的寻找)

今天在调试程序时,由于需要知道focus到底是在表格上,还是在表格中的editor中,这个问题一般情况下,需要在程序代码中打印下来focusOwner,但是今天又发现了另外一种方式来做,在这里共享给大家,有助于java 程序调试便捷高效. 另外这种方法就是,运行Eclipse,然后呢,程序跑起来之后,按键盘的快捷键:ctrl+shift+alt+F,按一下,程序界面中就会出现focus状态,显示目前光标focus到的是哪个控件,如果再按一下,此focus功能就关闭了.太神奇了,既高效有便捷的一种

一个电脑上如何登录两个微信或是多个微信

转:https://jingyan.baidu.com/article/c74d6000deb1ec0f6a595dce.html 1 首先,我们先下载微信PC版,安装好.(建议在电脑的桌面上留一个快捷方式) 步骤阅读 2 找到桌面上的"微信"快捷方式(的图标),单击右键显示"菜单",选择最后一个"属性"选项. 步骤阅读 3 在打开的属性窗口中,我们看到已经默认选中了"目标"中的文本内容(即:微信应用程序在电脑中的位置),我们