设为首页 加入收藏

TOP

归并排序(MergeSort)(二)
2015-07-20 17:25:10 来源: 作者: 【 】 浏览:9
Tags:归并 排序 MergeSort
ge(int* low, int* mid, int* high, int* right, int* helper) { ///收缩左边界,不再考虑左区间原本位于正确位置的元素 while(*low <= *right) if(++low >= mid) return; ///如果左区间的元素全部在正确位置,那么右区间也是如此,直接返回 int* left = low; ///设置左区间遍历指针 *(helper++) = *(right++); ///别浪费上面循环失败的比较结果。。。 if(right >= high) ///右区间空了 while(left < mid) *(helper++) = *(left++); ///把左区间剩下的复制过去 else while(true) { if(*left <= *right) ///相等时下标小的优先,使得算法稳定 { *(helper++) = *(left++); if(left >= mid) break; ///左区间扫描完直接跳出外层循环,此时右区间剩下来的元素本来就处于正确位置 } else { *(helper++) = *(right++); if(right >= high) ///右区间空了 { while(left < mid) *(helper++) = *(left++); ///把左区间剩下的复制过去 break; ///跳出外层循环 } } } while(right > low) *(--right) = *(--helper); ///再复制回来,不过要跳过右区间剩下的元素 } 虽然在最坏情况下和原始的归并函数一样,但是大部分情况还是有优化的,特别是当数组原本有序时,每层只需简单遍历O(n/2)个元素,比快速排序更高效。

减少的“倒腾”次数的期望

即下面cnt的平均值:
int cnt = 0;    ///计数器

void merge(int* low, int* mid, int* high, int* right, int* helper)
{
    while(*low <= *right)
    {
        cnt++;  ///左边多一个元素不用参与复制
        if(++low >= mid)
        {
            cnt += high - right;    ///右边都不用参加
            return;
        }
    }
    int* left = low;
    *(helper++) = *(right++);
    if(right >= high)
        while(left < mid) *(helper++) = *(left++);
    else while(true)
        {
            if(*left <= *right)
            {
                *(helper++) = *(left++);
                if(left >= mid)
                {
                    cnt += high - right;    ///右边不用参加复制的元素个数
                    break;
                }
            }
            else
            {
                *(helper++) = *(right++);
                if(right >= high)
                {
                    while(left < mid) *(helper++) = *(left++);
                    break;
                }
            }
        }
    while(right > low) *(--right) = *(--helper);   ///再复制回来,不过要跳过右区间剩下的元素
}
当左右区间元素个数均为 k/2时,左区间不用参与复制的元素个数为 i 的条件为前 i 小的元素都被分在了左边,并且第 i + 1大元素被分在了右边,但第k/2大元素要单独考虑。 右边不用参与复制的元素个数和左区间是一样的,因为是对称的。于是得到下面的级数: \
下面是我的测试数据(元素互异且随机排列): \ 可以看出这项优化平均减少O(n)次多余的操作。

使叶子“变粗”

同样归并排序也可以用插入排序来优化,同样也是因为对于小规模的数据插入排序常数小的缘故。并且由于引进插入排序我们知道叶子宽度一定大于1,因此可以简化归并函数:
#define FACTOR 10    ///叶子宽度
#define MID(i) (i >> 1) /// i / 2

int* helper;    ///辅助数组

/**********************************************
    函数:优化版归并函数
    说明:合并有序区间[low, mid)和[mid, high)
          right为右区间的遍历指针
          helper为局部变量覆盖全局声明
          这样做是为了减少代码行数
    时间复杂度:O(high - low)
**********************************************/
void merge(int* low, int* mid, int* high, int* right, int* helper)
{
    ///收缩左边界,不再考虑左区间原本位于正确位置的元素
    while(*low <= *right)
    {
        if(++low >= mid)
            return;  ///如果左区间的元素全部在正确位置,那么右区间也是如此,直接返回
    }
    int* left = low;    ///设置左区间遍历指针
    *(helper++) = *(right++);   ///别浪费上面循环失败的比较结果。。。
    ///因为叶子大于1,所以之前那两句就不用了。
    while(true)
    {
        if(*left <= *right)  ///相等时下标小的优先,使得算法稳定
        {
            *(helper++) = *(left++);
            if(left >= mid) break; ///左区间扫描完直接跳出外层循环,此时右区间剩下来的元素本来就处于正确位置
        }
        else
        {
            *(helper++) = *(right++);
            if(right >= high) ///右区间空了
            {
                while(left < mid) *(helper++) = *(left++); ///把左区间剩下的复制过去
                break;  ///跳出外层循环
            }
        }
    }
    while(right > low) *(--right) = *(--helper);   ///再复制回来,不过要跳过右区间剩下的元素
}

/*************************************
    函数:优化版插入排序
    说明:对区间[low, high)的数据排序
    时间复杂度:O(n + inverse)
*************************************/
static void improvedInsertionSort(int* low , int* high)
{
    for(int* cur = low; ++cur < high; )     ///实际是从第二个元素开始插入,因为第一个已经有序了
    {
        int tmp = *cur; ///临时保存要插入的值
        int* destPos = cur;     ///记录当前要插入的元素的正确安放位置,这里初始化为本来的位置
        ///把第一次测试单独提出来
        if(*(--destPos) > tmp)
        {
            do
            {
                *(destPos + 1) = *destPos;
            }while(--destPos >= low && *destPos > tmp);     ///测试上一个是否是目标位置
            *(destPos + 1) = tmp;   ///最后一次测试失败使得destIndex比实际小1
        }
    }
}

/*****************************************
    函数:归并排序
    说明:对区间[low, high)范围的数据排序
    时间复杂度:O(nlgn)
*****************************************/
void mergeSortRoutine(int* low, int* high)
{
    int range = high - low; ///区间元素个数
    if(range > FACTOR)   ///对于规模为1的子问题本身已经是解了,所以只处理规模大于1的子问题
    {
        int* mid = MID(range) + low; ///求出分割点
        ///递归求解子问题
        mergeSortRoutine(low, mid);
        mergeSortRoutine(mid, high);
        merge(low, mid, high, mid, helper);   ///再合并两个子问题
    }
    else improvedInsertionSort(low, high);
}

/****************************************
    函数:归并排序“外壳”
****************************************/
void mergeSort(int* low, int* high)
{
    helper = new int[high - low];   ///辅助数组最多也就存输入的元素数
    if(helper != nullptr)
    {
        mergeSortRoutine(low, high);
        delete[] helper;    ///释放内存
    }
    else return;    ///空间不足,没法启动归并排序
}

自底向上的归并排序

上面递归版的归并排序的分解步骤是通过划分父问题才得到子问题,但其实子问题是可以被我们直接找到的,因为一个子问题的标识是一个区间,而区间是由左右端点的数字确定的。 所以我们可以直接计算出我们目前想得到的子问题的区
首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C++算法之 判断是否为平衡二叉树 .. 下一篇C++学习:关于“std::vector(Type..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·求navicat for mysql (2025-12-26 13:21:33)
·有哪位大哥推荐一下m (2025-12-26 13:21:30)
·MySQL下载与安装教程 (2025-12-26 13:21:26)
·Linux_百度百科 (2025-12-26 12:51:52)
·Shell 流程控制 | 菜 (2025-12-26 12:51:49)