1. 程式人生 > 其它 >為什麼演算法容易忘記之快速排序

為什麼演算法容易忘記之快速排序

本文用來幫助大家理解記憶快速排序,方法和上篇文章一樣,著重理解演算法基本思想及其程式碼中的迴圈控制變數的意義。

基本思想

快速排序屬於拿著元素找位置的演算法。思路非常簡單明瞭,首先給第一個元素找到它正確的位置並把它放置其中,此時該元素將原陣列分為兩半,左半邊的元素都小於或等於它,右半邊的元素都大於它,對這兩個子陣列重複剛才的操作,直到子陣列中只有一個元素,此時排序完成。

由思想到程式碼

首先,我們用一個forInsert變數儲存陣列第一個位置上的元素的值。可以通俗理解為我們將第一個位置上的元素“挖”了出來,以便為它找到合適的位置,第一個位置此時已經是“空”的,位置是空的這一概念很關鍵,後面會用到。

如何為該元素找到合適的位置呢?答案是先確定該元素所在位置的範圍,不斷縮小該範圍,直到該範圍是一個確定的位置,查詢結束,把forInsert的值放到該位置上,再對該位置左右兩個子陣列進行迭代,直到子陣列大小為1時結束,排序完成。為表示該元素所在位置的範圍,我們需要定義兩個變數left,right,代表元素所在位置的範圍的左端和右端,顯然left的初始值應為0,right的初始值應為N-1。

下面開始縮小這一範圍,將right位置上的元素與forInsert進行比較,如果大於forInsert,那麼可以斷定right這個位置肯定不是forInsert應當在的位置,因為如果將forInsert放置在right位置上,該位置上原來的元素將無處安放。所以我們可以將right減1(right--),這就縮小了位置的範圍,然後我們繼續將新的right位置上的元素與forInsert比較,如果還是大於forInsert,則可以繼續將right減1後繼續比較,直到right位置上的值小於forInsert的值時,就是magic發生的地方。

由於right位置上的元素比forInsert小,我們無法判斷該位置是否是forInsert應當在的位置,BTW,我們可以判定left這個位置肯定不是forInsert應當在的位置,為什麼?請參照上文敘述自行理解。

然後呢?我們可以將right位置上的值放置到left位置上,讓left加1(left++),這進一步縮小了位置的範圍。

此時right位置我們認為是“空的”了,看到沒,剛才left是空的,現在right是空的了。

下步的思路肯定還是想辦法繼續縮小位置的範圍。我們可以將left位置上的元素與forInsert比較,如果小於forInsert的值,我們可以斷定,left這個位置肯定不是forInsert應當在的位置,為啥?因為將forInsert放置到該位置上,該位置上的元素只能往左邊挪,而左邊每個位置上都是比forInsert小的元素導致“無處可挪”,矛盾出現,反證結束。然後我們又可以放心地將left加1了,位置範圍又縮小了,哦耶!

我們繼續將left位置上的元素與forInsert比較,直到發現left位置上的元素大於forInsert時,又要有magic發生了,我們將left位置上的元素放置到right位置上(還記得right位置此時是空的嗎?),現在,left位置變成空的了,由於此時right位置上的元素是大於forInsert的,right位置肯定不是forInsert應當在的位置,所以我們要將right減1,進一步縮小待確定位置的範圍。

好了,讓我們停一停,看看現在是什麼狀況,顯然left增加了一些值,並且left位置此時是空的,right減少了一些值,整體上[left , right]包含的範圍比初始時小了好多。如果left=right,我們知道,要找的位置就是現在left所指示的空位置,直接將forInsert放置到left位置上即可。然後開始左右兩個子陣列的迭代,如果left還是小於right,那我們只能繼續進行縮小位置範圍的工作,直到確定位置為止。

這是程式碼

void quick_sort(int s[], int l, int r)
{
if (l < r)
{
int i = l, j = r, x = s[l];
while (i < j)
{
while(i < j && s[j] >= x)
j--;
if(i < j)      s[i++] = s[j];
while(i < j && s[i] < x)
i++;
if(i < j)    s[j--] = s[i];
}
s[i] = x;
quick_sort(s, l, i - 1); // 遞迴呼叫
quick_sort(s, i + 1, r);
}