有序区间的归并排序 cpp实现

作业2-9

设子数组a[0:k-1]和a[k:n-1] ( 0 < = k < = n − 1 ) (0<=k<=n-1) (0<=k<=n1)已经排好序。试设计一个合并这两个子数组为排好序的数组a[0:n-1]的算法。要求算法在最坏的情况下所用的计算时间为 O ( n ) O(n) O(n),而且只用到 O ( 1 ) O(1) O(1)的辅助空间。

思路见链接

本题最需要的就是寻找可以作为归并的辅助空间。先看选在哪里,不能重新开辟和n的大小有关个空间,那样空间复杂度就不是O(1),那能不能开辟常数个空间呢?比如说一个,没证明过,但码者认为不能写出通用且作为归并辅助空间的代码。还有就是申请 max ⁡ ( a ) \max(a) max(a)个空间做计数排序,这个不需要。问题来了不知道 max ⁡ ( a ) \max(a) max(a)且如果大于 1 e 7 1e7 1e7直接爆空间。

只能逼迫着我们自己借用num的空间。假如我们要归并一个2*m个的数应该借用空间,按照归并排序来说应该需要2*m,
但其实m个也能实现只是数组下标会比较乱且不能递归。但是归并的时候一定会打乱辅助空间我们需要给辅助空间里的数进行排序,这就要求我们借出来的空间不能太大,最大是 O ( n ) O(\sqrt n) O(n )(普通排序需要 O ( n 2 ) O(n^2) O(n2)的时间),为了之后方便令 m = ⌈ n ⌉ m = \left \lceil \sqrt n \right \rceil m=n

现在我们就用着 O ( n ) O(\sqrt n) O(n )个空间搞事情了。

由于待排的两个序列的长度也应该是m,我们可以将序列化成n个长度m(最后一个可能没有这么大)的子序列。那辅助选在哪呢?我觉得选在后面比较方便,理论上选连续的m个都可以,但要是 l e n t h ( a ) ( m o d   m ) = 0 ( m o d   m ) lenth(a)(mod\ m)=0(mod\ m) lenth(a)(mod m)=0(mod m)的之后某些操作会不方便,我们先把最后不能凑成m(为了方便写代码包括m)个的元素(即 a m ( ⌊ ( n − 1 ) / m ⌋ + 1 ) . . . a l e n a_{m (\left \lfloor (n-1)/m \right \rfloor+1)} ... a_{len} am((n1)/m+1)...alen)“丢弃”,将前m-2个序列排序按照第一个首元素排序。然后再通过将第m-1个序列所在的空间作为辅助空间(swap函数可以实现辅助空间的信息不会丢失,只是顺序会被打乱)进行归并,这样前m-2个序列的所有元素都应该完成了排序。

解释:前m-2个区域中相邻的三个区域最少有个二个区域来自原始序列中的同一个有序部分。(这个地方好好思考!)如果我对这任意两个相邻的两个序列归并,那么所得到的第一个序列一定比后面的待归并的序列小,也就是说,对于这m-2个序列(假设有s个)中这个序列相对于后面的位置以确定。

然后将后2s个元素,任意一种不超过 O ( n 2 ) O(n^2) O(n2)的排序算法。这时候前面是有序的后面也是有序的了。也就是说我们通过以上步骤只是将后面的序列减小到 O ( n ) O(\sqrt n) O(n )量级,而且最后最大的s个元素一定在最后面s个元素。

最后再我们只需要将最后s个序列作为辅助空间,做与之前类似的逆向归并。然后再将最后s个元素排序。就得到排序序列。

注意:当m<4的时候会使得某次排序访问数组越界,所以直接让m<4时,大约在9个元素以内的时候,直接调用排序算法。这样只需要添加少量的,并且不会影响在数据较大的时候的时间。

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

const int N = 1e5+7;
int num[N] = {8,9,10,1,2,3,4,5,6} ;

void swap(int &a ,int &b){
    int tmp = a;
    a = b;
    b = tmp;
}

void swap_array(int a[], int b[], int len){
    if(a == b)
        return;
    for(int i=0;i<len;i++)
        swap(a[i], b[i]);
}


void merge_positive(int arr1[], int arr2[],  int reg[], int len) {
    swap_array(arr1, reg, len);
    int *p=arr2, *q=reg, *r = arr1;
    while (p != arr2+len && q != reg+len)
        if(p == r)
            break;
        else if(*p > *q)
            swap(*q++, *r++);
        else
            swap(*p++, *r++);
    while (q != reg+len)
        swap(*q++, *r++);
}

void merge_opposite(int arr1[], int arr1len, int arr2[],  int reg[], int len) {
    swap_array(arr2, reg, len);
    int *p=arr1+arr1len-1, *q=reg+len-1, *r = arr2+len-1;
    while (p != arr1-1 && q != reg-1)
        if(p == r)
            break;
        else if(*p > *q)
            swap(*p, *r),r--,p--;
        else
            swap(*q, *r),r--,q--;
    while (q != reg-1)
        swap(*q--, *r--);
}

void selection_sort_array(int arr[], int m, int n)
{
    int i,j;
    for (i = 0 ; i < m - 1 ; i++)
    {
        int min = i;
        for (j = i + 1; j < m; j++)
            if (*(arr+n*j) < *(arr+min*n))
                min = j;
        swap_array(arr+min*n, arr+n*i, n);
    }
}

void merge_array(int arr[], int len, int k){
    int n = sqrt(len);
    int m = (len-1)/n+1;
    if (m<4){
        sort(arr, arr+len);
	return;
    }
    swap_array(arr+(k-1)/n*m,arr+(m-2)*n, n);//O(n)
    
    selection_sort_array(arr, m-2, n);//O((n+m)n) -> O(len)
    
    for(int i=0;i<m-3;i++)//O(2n*m) ->O(len)
        merge_positive(arr+i*n, arr+(i+1)*n, arr+(m-2)*n, n);//O(2n)
    
    int s = len - n*(m-2);
    selection_sort_array(arr+len-s*2, s*2, 1);//O(4s^2)(n<=s<2n) -> O(len)
    
    for(int i=len-s*3;i>0;len-=s)//O(len/s *2s) -> O(len)
        merge_opposite(arr+i, s, arr+i+s, arr+len-s, s);//O(2s)
    
    merge_opposite(arr, len%s, arr+len%s+s, arr+len-s, s); //O(2s)
    
    selection_sort_array(arr+len-s, s, 1);//O(s^2) -> O(len)
}
int main(){
    int n = 9;
    int k = 3;
    //cin >> n >> k;
    //for(int i=0;i<n;i++)
    //    cin>>num[i];

    merge_array(num, n, k);

    for(int i=0;i<n;i++)
        cout << num[i] << ' ';
    cout<<endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值