自然合并排序算法

合并排序基本思想:

          将待排序元素分成大小大致相同(可以不等长)的两个子集和,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。

 

递归版:

    void MergeSort(int a[],int left,int right)
    {
        if(left<right)
        {
            int i=(left+right)/2;
            MergeSort(a,left,i);
            MergeSort(a,i+1,right);
            Merge(a,b,left,i,right);
            Copy(a,b,left,right);      //copy b to a
        }
    }


可见递归版的合并排序是严格按照其思想完成的。

最坏情况下:T(n)=2T(n/2)+O(n);

则其时间复杂度O(nlogn)

 

非递归:

    void MergeSort(int a[],int n)
    {
        int *b=new int[n];
        int s=1;
        while(s<n)
        {
            MergePass(a,b,s,n);    //合并到数组b
            s+=s;                                //这里不是自增,而是成倍的增加</span>
            MergePass(b,a,s,n);            //合并到数组A
             s+=s;
         }
    }
     
    void MergePass(int x[],int y[],int s,int n)
    {
         int i=0;
         while(i<=n-2*s)
        {
             Merge(x,y,i,i+s-1,i+2*s-1);            //这里只能完成标准区间内的合并排序,如果有多余的元素便顾及不了
             i=i+2*s;
         }
        if(i+s<n)                        //解决最后一个元素(区间块)合并而设计的,i=0,s=8,n=9
            Merge(x,y,i,i+s-1,n-1);
        else
            for(int j=i;j<=n-1;j++)            //未到合并最后一个元素的直接复制
                y[j]=x[j];
    }
     
    void Merge(int c[],int d[],int l,int m,int r)
    {
        //合并c[l:m]和c[m+1:r]到d[l:r]
        int i=l,j=m+1,k=r;
         while((i<=m)&&(j<=r))
        {
            if(c[i]<=c[j])
                d[l++]=c[i++];
            else
                d[l++]=c[j++];
        }
        if(i>m)                        
            for(int q=j;q<=r;q++)        //i>m说明i已经复制到d中了,所以要把剩下的j复制到b中,并且此时c中元素是有序的,所以可以直接复制
                d[l++]=c[q];
        else
            for(int p=i;p<=m;p++)       
                d[l++]=c[p];
    }


分析一下时间复杂度:

纵向:1->2->4->8....n     共logn次

横向:包括比较和循环复制,最多都不超过n

所以时间复杂度也为O(nlogn)


但在现实中给定数组中,肯定存在已排好序的子数组段,如果将其利用起来,就避免了无意义的比较。

例如:a[]={0,-1,9,3,6,4,2,5,7};

自然排好序的子数组段有{0}{-1,9}{3,6}{4}{2,5,7}.可以用一次对数组a的线性扫描,找出这些已排好序的子数组段,然后两两合并,构成更大已排好序的数组。

{-1,0,9}{3,4,6}{2,5,7}->{-1,0,3,4,6,9}{2,5,7}->{-1,0,2,3,4,5,6,7,9}


核心代码:


    void GetBPoint(int a[],int b[],int n,int &m)              //线性扫描a[],记录下已排好序的子数组段下标
    {
         int j=0;
         b[j++]=0;
        for(int i=0;i<n-1;i++)
         {
             if(a[i+1]<a[i])
                 b[j++]=i+1;
         }
         m=j;
         PrintArray(b,j);
    }

 


    void MergeSort(int a[],int n)
    {
        int *b=new int[n];
        int s=1;
        while(s<t_m)
        {
            MergePass(a,b,s,n);   //合并到数组b
            s+=s;
            MergePass(b,a,s,n);    //合并到数组a
            s+=s;
        }
        delete[] b;
    }
     
     
    void MergePass(int x[],int y[],int s,int n)
    {
        int i=0;
        while(i<=t_m-2*s)
        {
            int r=((i+2*s)<t_m)?t[i+2*s]:n;     //当自然排好序片段为偶数时,就r=n,表示刚好两两合并成功,最后没有多余的子数组段
     
            Merge(x,y,t[i],t[i+s]-1,r-1);
             i=i+2*s;
         }
         if(i+s<t_m)
             Merge(x,y,t[i],t[i+s]-1,n-1);
        else
            if(i<t_m)                                 //i<t_m表示有多余的子数组段,则直接复制在y[]后,因为该子数组已排好序了
             {
                 for(int j=t[i];j<=n-1;j++)
                    y[j]=x[j];
             }
    }


时间复杂度分析:

由于利用了自然排好序的子数组段,所以在自然合并排序中,合并的次数要少很多,即使是在某一具体实例中,而不是所谓的平均情况下。

尽管理论上分析时间复杂度也为O(nlogn),但是实际中,一定会存在自然排好序片段,这样的话时间就会大大降低了。


极端的例子,如果n元素数组已经排好序了,该算法并不需要合并,而合并排序算法还需要进行logn合并,

所以自然合并排序算法需要O(n)(时间仅仅花在扫描上了),合并排序算法需要O(nlogn)时间


自然合并排序完整代码:

    #include<iostream>
    using namespace std;
    #define N 9
     
    int t_m;      //自然合并排序中线性扫描的标记数
    int t[N];
     
    void Merge(int c[],int d[],int l,int m,int r)
    {
        //合并c[l:m]和c[m+1:r]到d[l:r]
        int i=l,j=m+1,k=r;
        while((i<=m)&&(j<=r))
        {
            if(c[i]<=c[j])
                d[l++]=c[i++];
            else
                d[l++]=c[j++];
        }
        if(i>m)                        
            for(int q=j;q<=r;q++)        //i>m说明i已经复制到d中了,所以要把剩下的j复制到b中,并且此时c中元素是有序的,所以可以直接复制
                d[l++]=c[q];
        else
            for(int p=i;p<=m;p++)       
                d[l++]=c[p];
    }
     
    void PrintArray(int a[],int n)
    {
        for(int i=0;i<n;i++)
            cout<<a[i]<<" ";
        cout<<endl;
    }
     
     
    void GetBPoint(int a[],int b[],int n,int &m)
    {
        int j=0;
        b[j++]=0;
        for(int i=0;i<n-1;i++)
        {
            if(a[i+1]<a[i])
                b[j++]=i+1;
        }
        m=j;
        PrintArray(b,j);
    }
     
     
     
     
     
    void MergePass(int x[],int y[],int s,int n)
    {
        int i=0;
        while(i<=t_m-2*s)
        {
            int r=((i+2*s)<t_m)?t[i+2*s]:n;
     
            Merge(x,y,t[i],t[i+s]-1,r-1);
            i=i+2*s;
        }
        if(i+s<t_m)
            Merge(x,y,t[i],t[i+s]-1,n-1);
        else
            if(i<t_m)
            {
                for(int j=t[i];j<=n-1;j++)
                    y[j]=x[j];
            }
    }
     
    void MergeSort(int a[],int n)
    {
        int *b=new int[n];
        int s=1;
        while(s<t_m)
        {
            MergePass(a,b,s,n);   //合并到数组b
            s+=s;
            MergePass(b,a,s,n);    //合并到数组a
            s+=s;
        }
        delete[] b;
    }
     
     
     
    int main()
    {
        int n=9;
        int a[]={0,-1,9,3,4,6,2,5,7};    //自然排好序子数组段为偶数个
        //int a[]={0,-1,1,3,8,4,2,5,7};          奇数
        PrintArray(a,n);
        GetBPoint(a,t,n,t_m);
        MergeSort(a,n);
        PrintArray(a,n);
        return 0;
    }
————————————————
版权声明:本文为CSDN博主「fir_dameng」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014034497/article/details/45716423

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自然合并排序是一种基于归并排序的排序算法,它利用已有的有序子序列进行合并,以减少比较和交换的次数。算法的思想如下: 1. 首先,将待排序的数组分割成若干个有序子序列,可以通过遍历数组,找到所有已经有序的子序列。 2. 然后,将相邻的有序子序列进行合并,直到整个数组完全有序。 下面是使用C++实现自然合并排序算法的代码示例: ```cpp #include <iostream> using namespace std; // 合并两个有序子数组 void merge(int arr[], int left, int mid, int right) { int n1 = mid - left + 1; // 左子数组长度 int n2 = right - mid; // 右子数组长度 // 创建临时数组来存储左右子数组的元素 int *L = new int[n1]; int *R = new int[n2]; // 将元素复制到临时数组中 for (int i = 0; i < n1; i++) { L[i] = arr[left + i]; } for (int j = 0; j < n2; j++) { R[j] = arr[mid + 1 + j]; } // 合并左右子数组的元素到原始数组 int i = 0, j = 0, k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 复制剩余的元素 while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } // 释放临时数组的内存 delete[] L; delete[] R; } // 自然合并排序算法 void naturalMergeSort(int arr[], int n) { if (n <= 1) { return; } // 寻找有序子序列并进行合并,直到整个数组有序 bool isSorted = false; while (!isSorted) { isSorted = true; int i = 0; while (i < n - 1) { int left = i; int mid = -1; int right = -1; // 找到第一个有序子序列的末尾 while (i < n - 1 && arr[i] <= arr[i + 1]) { i++; } mid = i; // 找到第二个有序子序列的末尾 while (i < n - 1 && arr[i] <= arr[i + 1]) { i++; } if (mid != n - 1) { right = i; } // 合并两个有序子数组 if (right != -1) { merge(arr, left, mid, right); isSorted = false; } } } } // 测试代码 int main() { int arr[] = {9, 2, 7, 1, 5, 3, 8, 6, 4}; int n = sizeof(arr) / sizeof(arr[0]); cout << "原始数组:"; for (int i = 0; i < n; i++) { cout << arr[i] << " "; } cout << endl; naturalMergeSort(arr, n); cout << "排序后数组:"; for (int i = 0; i < n; i++) { cout << arr[i] << " "; } cout << endl; return 0; } ``` 算法的时间复杂度为O(nlogn),其中n是数组的大小。自然合并排序是一种稳定的排序算法,它可以在原地进行排序,不需要额外的空间。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值