归并分类算法C++实现(递归/非递归/自然合并)

归并排序

归并分类的方法是算法分析中的重要内容,传统的归并排序(递归实现,空间复杂度为O(n))较为适合初学者理解分治法的思路,同时也很好地体现了递归分解的思想。

基本思想

(1)将待排序元素分成大致相同的两个子集合
(2)分别对两个子集合进行排序
(3)将排好序的两个子集合并成一个排好序的集合

一、递归法 (传统+插排改进)。

将长度为n的数组,逐次二分递归,分隔为n个自然有序的单个元素,再两两合并,最终合并为有序的整体。
具体 实现代码如下:

void mergesort(int* a,int low,int high){
    int mid = (low + high) / 2;
    if (low < right) {
        insert_mergesort(a, low, mid);
        insert_mergesort(a, mid + 1, high);
        merge(a, low, mid, high);
    }
}
void merge(int *a,int low ,int mid,int high ) {
    int i, j, k=low;
    int b[50] = { 0 };  //假设对50个元素非递减排序
    i = low;
    j = mid + 1;
    while (i <= mid && j <= high) {
        if (a[i] <= a[j]) {
            b[k] = a[i++];
        }
        else b[k] = a[j++];
        k++;
    }
    if (i > mid) {
        for (int t = j; t <= high; t++)
            b[k++] = a[t];
    }
    else
        for (int t = i; t <= mid; t++)
            b[k++] = a[t];
    for (k = low; k <= high; k++)
        a[k] = b[k];
}

当子集合的元素很少时,归并算法多数时间消耗在哪里?在递归的处理上!因此,当子集合的元素个数适当少时,采用在小规模集合上能有效工作的排序方法,而非继续划分,以此克服上述缺点带来的时间消耗。

所以,递归法还可以继续改进,当划分的子区间够小时(一般认为<=15个元素),采用插入排序法,能够加快排序过程。但要注意插入排序函数放置的位置!

void insertsort(int *a, int low,int high)
{
    for (int i = low + 1; i <= high; i++) {
        int tmp = a[i];
        int j = i - 1;
        while (tmp < a[j] && j >= low ) {
            a[j + 1] = a[j];
            j--;
        }
        a[j + 1] = tmp;
    }
}
void merge(int *a,int low ,int mid,int high ) {
    int i, j, k=low;
    int b[50] = { 0 };
    i = low;
    j = mid + 1;
    while (i <= mid && j <= high) {
        if (a[i] <= a[j]) {
            b[k] = a[i++];
        }
        else b[k] = a[j++];
        k++;
    }
    if (i > mid) {
        for (int t = j; t <= high; t++)
            b[k++] = a[t];
    }
    else
        for (int t = i; t <= mid; t++)
            b[k++] = a[t];
    for (k = low; k <= high; k++)
        a[k] = b[k];
}
void insert_mergesort(int* a,int low,int high){
    int tmp = high - low;
    int mid = (low + high) / 2;
    if (tmp>15) {
        insert_mergesort(a, low, mid);
        insert_mergesort(a, mid + 1, high);
        merge(a, low, mid, high);
    }
    insertsort(a, low, high);  //插入排序放置在递归出口处
}

二、非递归。

将单个元素两两配对后,再不断合并的方法,具体流程如下图所示:

在这里插入图片描述
如上图中,2^n 个元素排序容易想到合并方法,但是如果是其他情况呢?你能想到下图的合并方法吗?
在这里插入图片描述
对于非 2^n 个元素的序列,排序时总是残留一个小于每次合并size的子序列,例如7这个元素。要使得排序函数具有普适性,就要单独把残留项保留下来,在最后一步时,将前面排好的序列(一定含有2^n个元素)与残留项一起合并,得到最终的结果。

具体 实现代码如下:

//从low到mid是前一个子序列,mid+1到high是后一个子序列
void merge(int* a, int* b, int low, int mid, int high) {
    int k = low;
    int i = low;
    int j = mid+1;
    while (i <= mid && j <= high) {
        if (a[i] <= a[j]) {
            b[k++] = a[i++];
        }
        else b[k++] = a[j++];
    }
    if (i > mid) {
        for (int t = j; t <= high; t++)
            b[k++] = a[t];
    }
    else
        for (int t = i; t <= mid; t++)
            b[k++] = a[t];
    for (k = low; k <= high; k++)
        a[k] = b[k];
}
//从按照划分的子序列长度进行每一轮的合并
void merge_pass(int *a,int *b,int sum,int size) {
    int i;
    for (i = 0; i <= sum - 2 * size; i += 2 * size)
        merge(a, b, i, i + size-1, i + 2 * size - 1);
    if (i + size < sum)
        merge(a, b, i, i + size-1, sum - 1);
}
//排序函数
void bottom_up_mergesort(int* a,int low,int high) {
    int sum = high - low + 1;
    int b[50] = { 0 };     //假设对50个元素排序
    int size = 1;
    int k = low;
    while ( size < sum ) {
    	//当前合并的序列长度小于原序列长度时,需要将两个子序列合并
        merge_pass(a,b,sum,size);
        //合并之后的子序列长度变为之前的两倍
        size *= 2;
    };
}

三、自然合并排序法。

自然合并排序是合并排序算法的一种改进。对于初始给定的数组,通常存在多个长度大于1的已自然排好序的子数组段。
例如,若数组a中元素为 {4,8,3,7,1,5,6,2} , 则自然排好序的子数组段有 {4,8},{3,7},{1,5,6},{2} 。
将他们看做一个个元素集合,用merge()函数进行合并最终得到整体有序的元素序列。
按照如上过程,构成更大的排好序的子数组段 {3,4,7,8},{1,2,5,6},继续合并为 {1,2,3,4,5,6,7,8}。

在这里插入图片描述

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人间油物丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值