01 複合判斷條件下,怎麼寫快排

//  快速排序
//  嚴老太太的教科書中關於快速排序是這樣寫的(下例爲升序排序)

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:滿足什麼樣的條件的元素,要被往右扔
//  
//  這兩個問題搞明白了,寫排序算法的時候,腦子就不亂
//  學習數據結構與算法,要一步一步來,慢慢來,不要怕想的過程慢,不要怕思考的過程出錯
//  怕的是:思考的邏輯過程連貫,很多程序中的判斷條件按直覺寫,這樣寫出來的程序一跑就錯,一測就掛

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章