文章目錄
完全二叉樹對排序的謎之天賦
堆排序用了完全二叉樹的深度信息,排序效率很高。其實,用到完全二叉樹的排序算法,效率都很高。完全二叉樹很適合用來排序,可能是它本身的結構的原因吧。
歸併排序在某種意義上,也是利用/藉助了完全二叉樹,不過和堆排序利用的原理不同,它是利用了倒置的完全二叉樹。比如:
歸併排序的根本思想:先分解爲多個有序子序列,再把多個有序子序列合併爲一個有序序列
聽起來好像沒做啥,其實正是合併的過程在完成排序這個關鍵操作。分解步驟只是鋪墊,爲了後面和合並排序奠定物質基礎(即把整個長度爲n的待排序序列分解爲n個長度爲1的子序列。)。而且,只是代碼需要分解操作,如果我們人直接拿着一個無序表企圖使用歸併方法排序的話,根本用不着分解,因爲一開始就是分解好的呀,把他看做是n個單獨的有序數據就好了。
所以合併纔是最核心的操作,因爲是 合併這個動作完成了排序的目標,所以名字叫做合併排序/歸併排序,不叫分解合併排序。
展示一個更完整的示例。上圖只是展示了合併過程,沒有展示分解的過程。
代碼
我覺得歸併排序的思想理解起來很簡單,但是代碼好難實現啊,我看了好幾遍了,總是隻能看到外層淺層的東西,看不深入,有種鑽不透徹的感覺
遞歸版本
/*SqList * L*/
MergeSort(L->arr, 1, L->length);//主函數中的調用語句
/*MergeSort函數遞歸調用自己實現把整個無序表分解爲n個長度爲1的子序列*/
//第一個參數傳的是指針,不是按值傳遞,所以後續遞歸操作的都是這一個數組
void MergeSort(int arr[], int left, int right)
{
if (left >= right)
return;
int middle = (right + left) / 2;
MergeSort(arr, left, middle);
MergeSort(arr, middle+1, right);
Merge(arr, left, middle, right);
}
/*把arr數組中下標爲left到middle,和下標爲middle+1到right的兩部分合並起來*/
void Merge(int arr[], int left, int middle, int right)
{
int tmp[right - left + 1];//把待合併排序的數值放在這個數組裏暫存備用,而排序完成的數值則存在原數組arr中
//從arr中獲取待歸併的元素,arr是待排序的整個無序表
for (int i=left;i<=right;++i)
{
tmp[i - left] = arr[i];
}
int i = left, j = middle + 1;
//給arr的下標left到下標right重新賦值(即賦排序完成後的值)
for (int k = left;k < right;++k)
{
//注意左邊組和右邊組分別都已經是有序的,這裏只是合併他倆得到一個有序數組,但並未新建一個數組,而是仍然放在原數組arr中
if (i>middle && j<=right)
{
arr[k] = tmp[j - left];//左邊組的數字已經排序完成,直接把右邊組的剩餘第一個數字放進arr
++j;
}
else if (j>right && i<=middle)//右邊組的數字已經排序完成,直接把左邊組的剩餘第一個數字放進arr
{
arr[k] = tmp[i-left];
++i;
}
//依次比較left和middle+1,left+1和middle+2,直到i爲middle或者j爲right
else if (tmp[i-left] <= tmp[j-left])
{
arr[k] = tmp[i - left];//等於較小的那個值
++i;
}
else if (tmp[i-left] > tmp[j-left])
{
arr[k] = tmp[j - left];//等於較小的那個值
++j;
}
}
}
複雜度和穩定性:佔用內存多,但是效率高,並且很穩定
時間複雜度:最好,最壞,平均都是
空間複雜度:
因爲待排序序列的每一個元素都要被分解出來,並兩兩比較處理啥的,所以要O(n)時間。
整個歸併排序需要和原始記錄序列同樣數量的存儲空間來輔助,並且遞歸深度是
所以是這麼多
兩兩比較無跳躍:穩定
迭代版本(時空複雜度都更低,比遞歸更優)
如果要用歸併排序,儘量用非遞歸的版本。因爲不僅空間複雜度更低(, 因爲不再需要遞歸調用的的棧空間),而且時間性能上也有提升。
我現在不喜歡遞歸了。之前一直覺得它的思路很智慧,把問題分解爲一個個子問題,分治和回溯的思想很美麗。但是現在我慢慢發現,遞歸併不是簡單的用空間換時間,它是時間和空間都需要的更多!!!很多個血淋淋的例子了,不忍回憶。
而且能把用遞歸很好理解的東西轉換爲迭代法實現,更聰明。
迭代版本不需要先分解,而是像咱們人類一樣,直接從最小的長度爲1的序列開始歸併。
void MergeSort_Iter(SqList * L)
{
int * TR = (int *)malloc(L->length * sizeof(int));
int k = 1;
while (k < L->length)
{
MergePass(L->r, TR, k, L->length);//把長度爲k的子序列歸併爲長度爲2k的子序列
k = 2 * k;//子序列長度加倍
MergePass(TR, L->r, k, L->length);
k = 2 * k;//子序列長度加倍
}
}
/*實現歸併,把SR數組中兩個相鄰的長度爲s的子序列兩兩歸併到TR數組*/
void MergePass(int SR[], int TR[], int s, int n)
{
int i = 1;
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];
}
//把有序的SR[i, ```, m]和有序的SR[m+1,```, n]歸併爲有序的TR[i,```,n]
void Merge(int SR[], int TR[], int left, int middle, int right)
{
int j,k,q;
//比較left和middle+1, left+1和middle+2···
for (j=middle+1, k=left;left<=middle && j<=right;++k)
{
if (SR[left] < SR[j])
TR[K] = SR[left++];
else
TR[k] = SR[j++];
}
//比較和歸併結束後(左邊組沒數字或者右邊組沒數字了)
if (left<=middle)//右邊組更短,右邊組沒數字了,把左邊組的剩餘有序數字SR[left,```,middle]直接複製到TR即可
{
for(q=0;q<middle-left;++q)
TR[k+1] = SR[left+1];//
}
if (j <= right)//左邊組沒數字了,右邊還有,把SR[j,```,right]複製到TR
{
for (q=0;q<right-j;++q)
TR[k+q] = SR[j+q];
}
}