您的位置:首页 > 其它

原地合并两个有序数组

2013-04-28 16:36 295 查看
    昨天看到一篇关于合并两个数组的题目:在一个大数组a
,a[0]~a[mid-1]和a[mid]~a[N-1]分别有序,怎样在O(1)的空间复杂度下完成将两个子数组合并到a
中。一开始想到了直接插入排序,前面是有序的嘛,直接从第mid个开始往前面插,这样一来空间复杂度满足要求,但是时间复杂度为O(N^2)。又回到了那个问题还有更好的方法吗?

    在网上看了一下资料,发现这是一个原地归并的问题,一般的归并是申请一个和已经排好序数组一样大的地址空间然后分别从两个数组中往新的空间添加最小的元素。

原地归并最大特点就是不能新申请空间,貌似这个算法是来自于编程珠玑上的手摇算法,算法描述如下

1.遍历器i,j分别记录数组一和二的首位置

2.判断数组一当前元素和数组二当前元素的大小

3.如果数组一中的元素大,遍历i加1,GOTO STEP 2

4.index记录当前j的值,判断数组一当前元素和数组二当前元素的大小

5.如果数组二中元素大,遍历器j+1,如果j<N,GOTO STEP 6,否则GOTO STEP 4

6.交换数组中i~index和index~j的元素

7.如果i<j&&j<N,如果j<N,GOTO STEP 2

8.结束

说了这么多还是借用一张图来说明



至于在交换内存的地方用到的算法和我一哥们去TX面试的时候算法考的差不多,将一个字符串一单词为单位逆序输出空间限制为O(1)

以下是实现代码:

#include <stdio.h>

void reverse(int *a,int length)
{
int i=0,temp;
if(length==1)
return;
for(i=0;i<length/2;i++)
{
temp=a[i];
a[i]=a[length-1-i];
a[length-1-i]=temp;
}

}

void swap(int *a,int left,int right)   //逆序
{
reverse(a,left);
reverse(&a[left],right);
reverse(a,right+left);
}

void main()
{
int index;
int a[10]={1,3,4,6,10,2,5,7,8,9};
int i=0,j=10/2;
for(i=0;i<10;i++)
printf("%3d",a[i]);
printf("\n");
i=0;
while(i<j&&j<10)
{
while(a[i]<a[j])
i++;
index=j;
while(a[j]<a[i])
{
j++;
if(j==10)	//这里要注意,如果在一次对数组二遍历时,里面的值都比当前a[i]要小,这里可能会越界!
break;
}
swap(&a[i],index-i,j-index);
i+=j-index;		//每次往i前面插入j-index,计数器i要跟着改变
}
//reverse(a,10);
for(i=0;i<10;i++)
printf("%3d",a[i]);
printf("\n");
getchar();
}

最后来看看它的时间复杂度,遍历一次数组用时O(N),每次交换内存用时O(N),这么一来时间复杂度就为O(N^2),也就是说在最坏的情况下(即数组一的元素全部都比数组二大的时候)和插入排序的时间复杂度是一样的,这个算法的优势体现在两个数组内部是有序的,而且当数据分布比较均匀时候,这个算法还是利用了数组而内部有序这么一个条件,没有进行多余的比较。而且在内存交换那一块计算量减少不小,如果有M和N长度的两块数据,用上述内存交换,需要运算M+N次,而用插入排序要M*N次。

那么如果有三个或者N个有序数组待合并且限定空间复杂度为O(1)呢?一样的,两个合并,然后在和最后一个合并。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息