// 快速排序
// 嚴老太太的教科書中關於快速排序是這樣寫的(下例爲升序排序)
void quicksort(int nums[], int low, int high)
{
if (low >= high)
return;
int first = low;
int last = high;
int key = nums[first];
while (first < last)
{
while (first < last && nums[last] >= key)
last--;
nums[first] = nums[last];
while (first < last && nums[first] <= key)
first++;
nums[last] = nums[first];
}
nums[first] = key;
quicksort(nums, low, first - 1);
quicksort(nums, first + 1, high);
}
// 算法剛開始的時候,將數組中的第一個元素用key保存了起來
// 再接着往下看
//
// 注意看第15行與18行的while循環的循環條件,以15行爲例
// 這代表的意思是,在 first < last 且 nums[last]>=key 的條件下,該循環會執行
// 執行的後果就是,last最終停留的位置將滿足以下兩個條件之一
// 1:last == first
// 2:last > first 且 last所標示的數組元素的值,是一個小於key的值
//
// 如果是第一種情況,17實際上什麼也不做,因爲這時last == first
// 如果是第二種情況,17所幹的事就是:將last所標示的那個數組元素扔到左邊去!
// 暫時不管第1種情況,單論第二種狀況的話,在外層循環的第一輪,16行執行結束後的狀態是:
// nums[last]的值已經被扔到左邊nums[first]的位置保存了起來,即nums[first]的值遭到覆寫
// 不過不要擔心,之前舊的nums[first]值被key保存了起來
// 當前,nums[last]的值被保存了兩次,一次是在當前的last位,一次是在當前的first位
// 即數組中保存的數值比之前小了一個,舉個例子,就是容量爲20的數組保存了19個數據,其中有一個數據保存了兩份,缺失的那個數據被暫存在key中
//
// 接下來以同樣的思路看18行的while循環,當20行執行結束後,狀態是:
// first將標示一個大於key的值,這時first左邊的所有值都小於key,而first當前標示的這個值則被扔到右邊nums[last]的位置保存了起來
// nums[last]的值遭到覆寫,不過不用擔心,上面說過了,被覆寫的值之前被保存了兩次
//
// 這樣內層的兩個循環,除也互相扔值之外,還有下面的功效
// 15行的循環被執行,會造成last標示的值被存儲了兩次
// 18行的循環被執行,會造成first標誌的值被存儲了兩次
//
// 這樣第一輪外層循環結束之後
// first左邊全是 <=key 的值,但nums[first]本身 > key
// last右邊全是 >=key的值,包括nums[last]本身也 >=key
// first與last之中,某個人標示的值被存儲了兩次
//
// 第二輪外層循環結束之後,數組的狀態描述起來與上面的狀態相同,不同的是,first與last之間的距離在縮小
// 當外層循環執行多次之後,達到 first == last 的狀態時,將跳出外層循環,這時我們用pos來稱呼當前first與last的共同標示位置
// 那麼這時數組的狀態是這樣的:
// pos左邊,全是 <= key的值
// pos右邊,全是 >= key的值
// pos本身所標示的值,已被其左邊或右邊某個位置備份
//
// 這個時候,執行第23行,將最早存儲在Key中的值覆寫到pos位置,這樣,缺失的數據被保存回來了
//
// 從27~64行之間的註釋,描述了快速排序遞歸中的第一層所做的事情
// 就是以數組中第一個元素爲標杆,將所有小於它的元素挪到它左邊,將所有大於它的元素挪到它右邊
// 這樣,遞歸的對它左邊及右邊的數組做同樣的操作,直到某層遞歸過程中,數組的容量爲1
// 注意看函數形參列表中的low與high,當數組容量爲1時,low == high,遞歸將解除
//
// 之上我們描述了單一的判斷條件下的快速排序,現在我們設想一個這樣的問題
// 我們給如下的一個結構體數組進行排序,數組中的元素代表學生
typedef struct{
int sno; // 學號
int score; // 成績
} STUDENT;
// 排序的要求是,按成績升序排列,當成績相同時,按學號降序排列
// 這種情況下,我們如何改寫快速排序的算法呢?
//
// 顯然,麻煩來源於【當成績相同時。。。。】
// 這個時候,我們再來看15行與18行的循環條件
while (first < last && nums[last] >= key) // 15
last--;
nums[first] = nums[last];
while (first < last && nums[first] <= key) // 18
first++;
nums[last] = nums[first];
// 我們明白15行的循環及其之後的【nums[first] = nums[last];】語句一起,總功效是將某個元素【往左扔】
// 而對應的18行的循環的功效則是【往右扔】
// 什麼樣的元素能往左扔?什麼樣的元素能往右扔呢?我們加一點小小的改動
while (first < last && !(nums[last] < key)) // 15
last--;
nums[first] = nums[last];
while (first < last && !(nums[first] > key)) // 18
first++;
nums[last] = nums[first];
// 這樣,左扔的條件是: 在first<last的前提下 !(nums[last] < key)
// 右扔的條件是: 在first<last的前提下 !(nums[last] > key)
// 這樣邏輯上看起來是不是很舒服?
//
// 這時再來分析,在STUDENT[]中,誰該向左扔,誰該向右扔
// 用語言來描述的話,左扔的條件是:
// 1:分數小於key,要左扔
// 2:當分數等於key時,若學號大於key,要左扔
// 條件1與條件2是或的關係,即滿足其中任何一個即可,而且兩個條件是明顯互斥的
// 再用代碼描述一下左扔的條件
(stu[last].score < key.score) || (stu[last].score == key.score && stu[last].sno > key.sno)
//
// 這樣,照貓畫虎,可以寫出STUDENT[]快速排序的第15行與18行循環條件
while (first < last && !( (stu[last].score < key.score)||(stu[last].score == key.score && stu[last].sno > key.sno) ) ) // 15
last--;
stu[first] = stu[last];
while (first < last && !( (stu[last].score > key.score)||(stu[last].score == key.score && stu[last].sno < key.sno) ) ) // 18
first++;
stu[last] = stu[first];
// 這樣,複合判斷條件下的快速排序算法就這樣寫出來了
//
// 當排序的判斷條件是複合條件的時候,要寫快速排序,就要搞明白兩個重大的問題
// 1:滿足什麼樣的條件的元素,要被往左扔
// 2:滿足什麼樣的條件的元素,要被往右扔
//
// 這兩個問題搞明白了,寫排序算法的時候,腦子就不亂
// 學習數據結構與算法,要一步一步來,慢慢來,不要怕想的過程慢,不要怕思考的過程出錯
// 怕的是:思考的邏輯過程連貫,很多程序中的判斷條件按直覺寫,這樣寫出來的程序一跑就錯,一測就掛
01 複合判斷條件下,怎麼寫快排
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.