前言
歸併排序,是建立在歸併操作上的一種有效的排序演算法,效率為O(nlogn)。1945年由約翰·馮·諾伊曼首次提出。該演算法是採用分治法的一個非常典型的應用,且各層分治遞迴可以同時進行。(引自維基百科)
原理
以上視訊轉自www.youtube.com/watch?v=JSc…
由於掘金中無法載入視訊,可以直接點選YouTube(需vpn)連結,也可到我部落格的原文連結中觀看perkyrookie.github.io/PerkyRookie…
用一張圖片簡單來說就是這樣
示例程式碼(非遞迴)
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100 //用於要排序陣列的最大值
typedef struct //定義一個順序表結構
{
int r[MAXSIZE+1]; //用於儲存要排序陣列,r[0]用作哨兵或者臨時變數
int length; //用於儲存順序表的最大長度
}SqList;
void Merge(int *S, int *T, int low, int m, int high);
void MergePass(int *S, int *T, int s, int length);
void MergeSort(SqList *L);
int main()
{
int i;
int array[] = {39,80,76,41,13,29,50,78,30,11,100,7,41,86};
SqList L;
L.length = sizeof(array)/sizeof(array[0]); //獲取陣列長度
for(i=0;i<L.length;i++)
L.r[i+1]=array[i]; //把陣列存入順序表結構
MergeSort(&L);
for(i=0;i<L.length;i++) //輸出排序後的陣列
printf("%d ",L.r[i+1]);
return 0;
}
void MergeSort(SqList *L)
{
//申請額外的空間,由於r[0]為哨兵,所以這裡長度要+1
int* TR = (int *)malloc((L->length+1)*sizeof(int));
int k = 1;/*k用來表示每次k個元素歸併*/
while (k < L->length)
{
//k每次乘以2是遵循1->2->4->8->16...的二路歸併元素個數的原則
MergePass(L->r, TR, k, L->length);/*先借助輔助空間歸併*/
k *= 2;
MergePass(TR, L->r, k, L->length);/*再從輔助空間將排過序的序列歸併回原陣列*/
k *= 2;
}
free(TR);//釋放記憶體
TR=NULL;
}
//將S中相鄰長度為s的子系列兩兩歸併到T中
//這段程式碼稍微難理解點,文章後面會分析下
void MergePass(int *S, int *T, int s, int length)
{
int i = 1; //r[0]用作哨兵或者臨時變數
int j;
while (i <= length-2*s+1) //length-i+1(未合併元素) >= 2s
{
Merge(S, T, i, i+s-1, i+2*s-1);
i= i+2*s; //自增的下一個元素的起始點
}
if (i< length-s+1) //歸併最後兩個序列
Merge(S, T, i, i+s-1, length);
else
for (j = i; j <= length; j++)
T[j] = S[j];
}
//歸併排序最主要實現函式
//將有序的S[low..m]和S[m+1..high]歸併到有序的T[low..high]中
void Merge(int *S, int *T, int low, int m, int high)
{//j在[m+1,high]區間遞增,k用於目標T的下標遞增, l是輔助變數
int j, k, l;
for (k = low,j = m+1; low <= m && j <= high; k++) //將S中的元素由小到大歸併到T中
{
if (S[low] < S[j])
T[k] = S[low++]; //此處先執行T[k] = S[low],然後low++;
else
T[k] = S[j++]; //同理
}
//for迴圈過後,可能有j>high 或者 low>m,故分情況處理
if (low <= m)
{
for (l = 0; l <= m-low; l++)
T[k+l] = S[low+l]; //將剩餘的S[low..m]複製到T中
}
if (j <= high)
{
for (l = 0; l <= high-j; l++)
T[k+l] = S[j+l]; //將剩餘的S[j..high]複製到T中
}
}複製程式碼
這裡對部分難以理解的程式碼做個詳細分析:
void MergePass(int *S, int *T, int s, int length)
{
int i = 1; //r[0]用作哨兵或者臨時變數
int j;
while (i <= length-2*s+1) //length-i+1(未合併元素) >= 2s
{
Merge(S, T, i, i+s-1, i+2*s-1);
i= i+2*s; //自增的下一個元素的起始點
}
if (i< length-s+1) //歸併最後兩個序列
Merge(S, T, i, i+s-1, length);
else
for (j = i; j <= length; j++)
T[j] = S[j];
}複製程式碼
第5~9行:這裡實現的是子序列的兩兩歸併。
判斷條件
i <= length-2*s+1
可以看做length-i+1 >= 2s
,表示未合併的元素小於2s時跳出迴圈,即不足以構成兩個長度為s的子序列時跳出迴圈;未合併元素需要+1的原因可以用個例子解釋:即第一次進入迴圈時,應該是有length個元素未合併,而i起始大小是1,所以這裡要+1。
第7行:傳入元素-1是因為
i+s
和i+2s
都是下一個序列的起始點,所以是一個序列的終點就是這兩個值-1。
第10~14行:判斷條件i< length-s+1
一樣可以看做length-i+1>s
滿足條件:未合併的元素長度大於s,表示還能構成一個長度為s的序列和一個小於s的序列,即最後兩個序列,這時還要執行一次歸併。
else:未合併的元素長度小於s,則只剩一個序列,這時無法歸併,直接把元素加到T陣列後端。
剩下的應該不難理解了吧。
轉載請宣告:
文章作者:窗外蟋蟀
原始連結:https://juejin.im/post/5c2b41e55188256b9e0f2f0c