归并排序非递归+不回写优化实现
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
归并算法的基本思想:
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针达到序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
下面我们来分析一下归并排序的时间复杂度,一趟归并需要将A[1]~A[n]中相邻的长度为h的有序序列进行两两归并。并将结果放到B[1]~B[n]中,这需要将待排序序列中的所有记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行⌈log2n⌉趟,因此,总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。一般情况下,用归并是最好的,其他的排序算法都是建立在一定或者某种特殊的条件下才能表现出较好的性能,比如说快速排序,在数据相对无序的情况下,性能较好,插入排序在数据相对有序的情况下较好,等等。
需要移动的次数为O(2nlogn),需要的空间为2n。上课的时候老师提到了几种对归并排序的改进算法:
1.不回写:因为这样可以减少移动的次数,最简单的归并排序,每次对每两个有序表进行合并的时候都要保存到另外一个数组当中比如B【】数组,合并完之后要回写到原来的数组当中比如A【】。优化:我们可以这么做,比如当奇数次合并的时候是从A[]数组到B[]数组,偶数次是从B[]数组到A[]数组。这样可以减少一半的数据移动的次数
2.不递归:大家应该都知道,在进行递归调用的时候,需要对函数调用进行压栈出栈的操作,是非常的耗时间的,所以我们采用迭代法来代替递归对其进行改进,可以提高程序的性能。
3.其他:老师还讲到可以和插入相结合等等。
代码实现如下:
#include <iostream>
using namespace std;
int data[10]={8,7,2,6,9,10,3,4,5,1};
int cp[10];
void Merge(int a[],int b[],int first,int mid,int last) //合并两个有序序列
{
int p=first,q=mid+1;
int pos=first;
while(p<=mid&&q<=last)
{
if(a[p]<a[q])
{
b[pos++]=a[p++];
}
else
{
b[pos++]=a[q++];
}
}
if(p>mid)
{
while(q<=last)
{
b[pos++]=a[q++];
}
}
else
{
while(p<=mid)
{
b[pos++]=a[p++];
}
}
}
void MergePass(int a[],int b[],int gap,int n) //以一定的步长对数据进行合并
{
int i=0;
int j;
while(i<=n-2*gap+1)
{
Merge(a,b,i,i+gap-1,i+2*gap-1);
i=i+2*gap;
}
if(i<(n-gap))
Merge(a,b,i,i+gap-1,n-1);
else
for(j=i;j<n;j++)
b[j]=a[j];
}
void Merge_sort(int a[],int b[],int n) //归并排序的非递归 并且不进行回写
{
int gap=1;
while(gap<n)
{
MergePass(a,b,gap,n);
gap=2*gap;
MergePass(b,a,gap,n);
gap=2*gap;
}
}
int main()
{
Merge_sort(data,cp,10);
for(int i=0;i<=9;i++)
cout<<data[i]<<endl;
system("pause");
return 0;
}