您的位置:首页 > 其它

归并算法及其优化

2015-07-31 17:14 225 查看
转载:勇幸|Thinking (http://www.ahathinking.com)

归并排序算法思想:分而治之

分解:把长度为n 的待排序列分解成 两个长度为n/2 的序列

治理:对每个子序列分别调用归并排序,进行递归操作。当子序列长度为1 时,序列本身有序,停止递归

合并:合并每个排序好的子序列

归并排序采用分治法(Divide and Conquer)的一个应用,先使每个子序列有序,再使子序列段有序。需要一个辅助数组,时间复杂度是O(nlogn)

1,原地归并排序

因为需要一个辅助数组,所以归并排序的空间复杂度是O(n),对其 进行优化后可以进行原地排序,额外空间为O(1)。

原地归并排序对Sort()函数进行优化,利用的核心思想是“反转内存”的变体,即“交换两段相邻内存块”

在了解原地归并的思想之前,先回忆一下一般的归并算法,先是将有序子序列分别放入临时数组,然后设置两个指针依次从两个子序列的开始寻找最小元素放入归并数组中;那么原地归并的思想亦是如此,就是归并时要保证指针之前的数字始终是两个子序列中最小的那些元素。



首先,图b ,第一个子序列的值与第二个子序列的第一个值20比较,如果序列一的值小于20,则指针i向后移,直到找到比20大的值,即指针i移动到30;经过b,我们知道指针i之前的值一定是两个子序列中最小的块。

图c,先用一个临时指针记录j的位置,然后用第二个子序列的值与序列一i所指的值30比较,如果序列二的值小于30,则j后移,直到找到比30大的值,即j移动到55的下标;

图d,经过图c的过程,我们知道数组块 [index, j) 中的值一定是全部都小于指针i所指的值30,即数组块 [index, j) 中的值全部小于数组块 [i, index) 中的值,为了满足原地归并的原则:始终保证指针i之前的元素为两个序列中最小的那些元素,即i之前为已经归并好的元素。

交换这两块数组的内存块,交换后i移动相应的步数,这个“步数”实际就是该步归并好的数值个数,即数组块[index, j)的个数。

得到下图:



2,插入归并

归并排序的时间复杂度为O(nlgn),一般来讲,基于从单个记录开始两两归并的排序并不是特别提倡,一种比较常用的改进就是结合插入排序即先利用插入排序获得较长的有序子序列,然后再两两归并(改进后的归并亦是稳定的,因为插入排序是稳定的)。

原因的:尽管插入排序的最坏情况是O(n^2),看起来大于归并的最坏情况O(nlgn),但通常情况下,由于插入排序的常数因子使得它在n比较小的情况下能运行的更快一些,因此,归并时,当子问题足够小时,采用插入排序是比较合适的。

只需要在Merge 中判断当子序列长度小于某个值的时候采用InsertSort,大于这个值正常采用Merge排序

只需要在Merge里面进行修改:

public void Merge(int[] arr,int[] temp,int l,int r){
if(r-l<1) return;

if(r-l+1<INSERT_BOUND){
//这里在下面的划分递归中,只要满足了条件,就会进入插入排序
insertSort(arr,l,r);
}else{
int mid = (l+r)/2;

internalMergeSort(arr,temp,l,mid);
internalMergeSort(arr,temp,mid+1,r);

Sort(arr,temp,l,mid,r);
}
}


复杂度分析

下面分析下插入归并排序最坏情况下的复杂度:假设整个序列长度为n,当子序列长度为k时,采取插入排序策略,这样一共有n/k个子序列。

子序列完成排序复杂度:最坏情况下,n/k个子序列完成排序的时间复杂度为O(nk)。证明:每个子序列完成插入排序复杂度为O(k^2),一共n/k个子序列,故为O(nk)。

子序列完成合并复杂度:最坏情况下,合并两个子序列的时间复杂度为O(n),一共有n/k个子序列,两两合并,共需要lg(n/k)步的合并,所以这些子序列完成合并的复杂度为O(nlg(n/k))。

改进后的插入归并排序的最坏情况的复杂度为O(nk+nlg(n/k)),这里k的最大取值不能超过lgn,显然如果k大于lgn,复杂度就不是与归并一个级别了,也就是说假设一个1000长度的数组,采用插入策略排序子序列时,子序列的最大长度不能超过10。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: