二路歸併排序
1,遞歸思想
假設初始列表含有n個記錄,則可以看成n個有序子序列,每個有序子序列的長度爲1,然後兩兩歸併,得到n/2個長度爲2的有序子序列,再兩兩歸併,如此重複下去,直到得到一個長度爲n的有序序列爲止。
2,遞歸實現二路歸併排序
#define MAXSIZE 10 //定義一個宏,是arr的大小,也是需要開闢數組的大小
//二路歸併,將TR2[m..t]和[t+1..n]歸併成有序序列
void Merge(int TR1[], int TR2[],int m, int t, int n)
{
int j,k;//j爲TR1[]的下標,j爲TR2[t+1..n]的下標,而m爲TR2[m..t]的下標
//將TR2中記錄由小到大歸併入TR1中
for(k = m,j = t+1; m <= t && j <= n; ++k)
{
if(TR2[m] < TR2[j])
TR1[K] = TR2[m++];
else
TR1[K] = TR2[j++];
}
//將剩餘的TR2[m..t]複製到TR1中
for(; m <= t; ++m,++k)
{ TR1[k] = TR2[m]; }
//將剩餘的TR2[t+1..n]複製到TR1中
for(; j <= n; ++j,++k)
{ TR1[k] = TR2[j]; }
}
//該函數將大量數據遞歸下去,直到數據有序
void MSort(int SR[], int TR1[], int m, int n)
{
int t; //中間值,來劃分歸併的前半部分和後半部分
int TR2[MAXSIZE];//輔助數組,用來存儲排歸併排序後的有序子序列
if(m == n)//遞歸終止條件 m==n時,此趟歸併完成
TR1[m] = SR[m];
else
{
t = (m+n) / 2; //將SR分爲[m..t]和[t+1..n]
Msort(SR,TR2,m,t);//將SR[m..t]歸併排序成有序數組放到TR2[m..t]
Msort(SR,TR2,t+1,n);//將SR[t+1..n]歸併排序成有序數組放到TR2[t+1..n]
Merge(TR1,TR2,m,t,n);//最後將TR2中的[m..t]與[t+1..n]合併成有序數組放到TR1,則TR1保存的便是歸併排序後的結果。
}
}
//該函數只是封裝了一個需要遞歸的函數
void MergeSort(int *arr, int n)
{
assert(arr != NULL && n > 1);
MSort(arr,arr,0,n-1);
}
3,歸併排序遞歸算法的時間複雜度
時間複雜度爲O(nlogn)
一趟排序需要將SR[0]~SR[n-1]中相鄰的長度爲h的有序子序列進行兩兩歸併,並將結果放入TR1[0]~TR1[n-1]中,這需要將待排序序列掃描一遍,耗費時間爲O(n),根據二叉樹的性質,整個歸併排序需要logn次,所以歸併排序的時間複雜度爲O(nlogn)。
空間複雜度爲O(n+logn),存儲交換結果的數組已經遞歸的棧空間。
歸併排序特點:空間比較佔內存,但排序效率高且穩定。
歸併排序的遞歸算法,大量的引用的遞歸,儘管代碼比較清晰,但其會消耗時間和空間上的性能,我們追求效率,所以讓我們來看看迭代方法。
4,歸併排序的迭代實現
#define MAXSIZE 10
//將SR[m..t]和[t+1..n]歸併的結果放入TR中
void Merge(int SR, int TR,int m, int t, int n)
{
int j,k;
for(j = t+1,k = m;m <= t && j <= n; ++k)
{
if(SR[m] < SR[j])
TR[k] = SR[m++];
else
TR[k] = SR[j++];
}
for(;m <= t; ++m,++k)
{
TR[k] = SR[m];
}
for(;j <= n; ++j,++k)
{
TR[k] = SR[j];
}
}
//將相鄰長度爲s的子序列歸併到TR中
void MergePass(int SR[], int TR[],int s, int n)
{
int i = 0;
int j;
while(i <= n - 2*s +1)
{
Merge(SR,TR,i,i+s-1,i+2*s-1);//兩兩歸併
i = i + 2 * s;
}
if(i < n-s+1) //歸併最後兩個序列
{
Merge(SR,TR,i,i+s-1,n);
}
else //歸併最後剩下的單個子序列
{
for(j = i; j < n; ++j)
TR[j] = SR[j];
}
}
void NiceMergeSort(int *arr, int n)
{
assert(arr != NULL && n > 1);
int *TR = (int*) malloc(sizeof(int) * MAXSIZE);
int k = 1; //k爲歸併長度 第一次將相隔爲1的數據進行歸併
while(k < MAXSIZE-1)
{
//將原來無序的數列兩兩歸併入TR
MergePass(arr,TR,k,MAXSIZE-1);
k *= 2; //歸併長度擴大一倍
//將兩兩歸併有序的數列,再次歸併入數組arr中
MergePass(TR,arr,k,MAXSIZE-1);
k *= 2;
}
}
5,非遞歸迭代的複雜度
非遞歸迭代的方法,避免了遞歸時深度爲logn的棧空間,空間上只申請了臨時數組TR,空間複雜度爲O(n),避免遞歸也在時間複雜度上有了一定的提升,因此,我們使用歸併排序,應該多使用迭代方法。