归并排序的几种变形

 

归并排序的基础概念就不讲了,我的博客只会写有创造性的东西。

 

归并排序的代码如下:

void mergesort(int* arr,int n){

if(n>1){

mergesort(arr,n/2);

mergesort(arr+n/2,n-n/2);

merge(buff,arr,n/2,arr+n/2,n-n/2);

memcpy(arr,buff,n);

}

}

 

迭代版

标准的归并排序是个递归过程,不过要用迭代过程表达也很简单,就是先两个两个一排,再四个四个一排。。。。

如:

98 15 73 20 76 27 34 82

15 98 20 73 27 76 34 82 //第一趟

15 20 73 98 27 34 76 82 //第二趟

15  20 27 34 73 76 82 98 //第三趟

 

代码不写了,代码比递归版的复杂一点,效率也提高了一点

 

原地归并算法

标准归并排序使用的归并算法要额外申请一个大小为n的数组暂时存放归并数据,原地归并算法就不用,不过要以时间为代价,它的一次归并过程的时间复杂度是O(n^2)

 

举个例子:

15 20 76 98 27 34 73 82

这里有两段有序数组,检测到应该把273473插入到2076之间,也就是把

76 98 27 34 73 这段数组循环左移两位。

如何把数组循环左移k位呢?

用的是翻大饼算法,先把前k个元素翻转过来,再把后n-k个元素翻转过来,在翻转这n个元素。

过程如下:

15 20 76 98 27 34 73 82 //检测到应该把27-73插入到2076之间

15 20 98 76 27 34 73 82 //翻转9876这段数组

15 20 98 76 73 34 27 82 //翻转273473这段数组

15 20 27 34 73 76 98 82 //翻转这5个元素

15 20 27 34 73 76 98 82 //前面的数组已经有序,继续归并后面的数组

 

假设待排序元素是均匀分布的,那么待归并的前后两段有序数组元素是交叉排列,一次翻大饼算法只能插入O(1)个元素,因此原地归并算法的时间复杂度是T(n)=T(n-1)+n,

得到T(n)=O(n^2)

 

那么使用了原地归并算法的归并排序的时间复杂度就是T(n)=2T(n/2)+n^2,得到T(n)=n^2

原地归并排序在效率上肯定是不如插入排序的,具体有什么优势暂时没想到。

 

交替归并排序

从标准归并排序的代码中可以看到,每次利用缓冲区完成归并算法后,又要把缓冲区的内容复制回原数组里面去,这浪费了一些时间。

利用缓冲区完成归并算法后,完全可以把缓冲区和原数组的身份对调,就省去了复制回去的时间。

98 15 73 20 76 27 34 82 //A数组

15 98 20 73 27 76 34 82 //B数组

15 20 73 98 27 34 76 82 //A数组

15  20 27 34 73 76 82 98 //B数组

 

这里有个问题,最后的有序序列在A数组还是在B数组,写代码的时候注意一下就可以了。

 

链表归并排序

链表归并排序很容易理解了,就是把前后两半先排完,再用链表归并算法。

 

链表归并排序不存在数组归并排序需要缓冲区的问题,好处还是很大的。

 

表归并排序

这个肯定又是我的重新发明了

不知道大家有没有看过严蔚敏写的《数据结构(C语言版)》里面的表插入排序呢?

同样的,归并排序也可以完美地加上一个“表”字。

刚才说过,数组归并排序需要额外申请一个缓冲区,链表归并排序却不存在这个问题。

在表归并排序里,申请缓冲区是必须的,但存的不是元素的副本,而是元素的下标,两个数组形成一个静态链表。

直接举例子:

一开始数组无序,初始化下标数组,全都初始为-1

0

1

2

3

4

5

6

7

98

15

73

20

76

27

34

82

-1

-1

-1

-1

-1

-1

-1

-1

 

第一趟归并,变成两个一组,15->98,20->73,27->76,34->82

0

1

2

3

4

5

6

7

98

15

73

20

76

27

34

82

-1

0

-1

2

-1

4

7

-1

 

 

 

 

第二趟归并,变成四个一组,15->20->73->98,27->34->76->82

0

1

2

3

4

5

6

7

98

15

73

20

76

27

34

82

-1

3

0

2

7

6

4

-1

 

第三趟,成为有序的静态链表

0

1

2

3

4

5

6

7

98

15

73

20

76

27

34

82

-1

3

4

5

7

6

2

0

 

这里又有个问题,需要记住每个链表的首元素的下标,这个很容易解决,这里不讨论了。

 

现在,链表已经有序了,还要把元素归位,要用的是严蔚敏写的《数据结构(C语言版)》重排记录的算法。

描述有点复杂,这里不写了,想了解自己看书去吧。

 

这个表归并排序算法,时间复杂度还是O(nlogn),空间复杂度还是O(n)

不过还是有一点优势的,特别是对于元素块头比较大的数组。

时间上说,在排序时元素不需要移动,只有在归位时,需要交换n-1次。

空间上说,申请的辅助空间只要存整型变量,不用存整个结构体。

也有点劣势,它毕竟是链表,不符合空间局部性原理,cache对它就没用了。

 

多路归并

归并多个有序数组,可以用堆,可以用败者树,也可以用哈夫曼树,不写了。

posted @ 2016-03-21 21:46  w43076  阅读(329)  评论(0编辑  收藏  举报