1. 程式人生 > >【轉】白話經典算法系列之六 快速排序 快速搞定

【轉】白話經典算法系列之六 快速排序 快速搞定

轉自:http://blog.csdn.net/morewindows/article/details/6684558

快速排序由於排序效率在同為O(N*logN)的幾種排序方法中效率較高,因此經常被採用,再加上快速排序思想----分治法也確實實用,因此很多軟體公司的筆試面試,包括像騰訊,微軟等知名IT公司都喜歡考這個,還有大大小的程式方面的考試如軟考,考研中也常常出現快速排序的身影。

總的說來,要直接默寫出快速排序還是有一定難度的,因為本人就自己的理解對快速排序作了下白話解釋,希望對大家理解有幫助,達到快速排序,快速搞定

快速排序是C.R.A.Hoare於1962年提出的一種劃分交換排序。它採用了一種分治的策略,通常稱其為分治法(Divide-and-ConquerMethod)。

該方法的基本思想是:

1.先從數列中取出一個數作為基準數。

2.分割槽過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊。

3.再對左右區間重複第二步,直到各區間只有一個數。

雖然快速排序稱為分治法,但分治法這三個字顯然無法很好的概括快速排序的全部步驟。因此我的對快速排序作了進一步的說明:挖坑填數+分治法:

先來看例項吧,定義下面再給出(最好能用自己的話來總結定義,這樣對實現程式碼會有幫助)。

以一個數組作為示例,取區間第一個數為基準數。

0

1

2

3

4

5

6

7

8

9

72

6

57

88

60

42

83

73

48

85

初始時,i = 0;  j = 9;   X = a[i] = 72

由於已經將a[0]中的數儲存到X中,可以理解成在陣列a[0]上挖了個坑,可以將其它資料填充到這來。

從j開始向前找一個比X小或等於X的數。當j=8,符合條件,將a[8]挖出再填到上一個坑a[0]中。a[0]=a[8]; i++;  這樣一個坑a[0]就被搞定了,但又形成了一個新坑a[8],這怎麼辦了?簡單,再找數字來填a[8]這個坑。這次從i開始向後找一個大於X的數,當i=3,符合條件,將a[3]挖出再填到上一個坑中a[8]=a[3]; j--;

陣列變為:

0

1

2

3

4

5

6

7

8

9

48

6

57

88

60

42

83

73

88

85

 i = 3;   j = 7;   X=72

再重複上面的步驟,先從後向前找,再從前向後找

從j開始向前找,當j=5,符合條件,將a[5]挖出填到上一個坑中,a[3] = a[5]; i++;

從i開始向後找,當i=5時,由於i==j退出。

此時,i = j = 5,而a[5]剛好又是上次挖的坑,因此將X填入a[5]。

陣列變為:

0

1

2

3

4

5

6

7

8

9

48

6

57

42

60

72

83

73

88

85

可以看出a[5]前面的數字都小於它,a[5]後面的數字都大於它。因此再對a[0…4]和a[6…9]這二個子區間重複上述步驟就可以了。

對挖坑填數進行總結

1.i =L; j = R; 將基準數挖出形成第一個坑a[i]。

2.j--由後向前找比它小的數,找到後挖出此數填前一個坑a[i]中。

3.i++由前向後找比它大的數,找到後也挖出此數填到前一個坑a[j]中。

4.再重複執行2,3二步,直到i==j,將基準數填入a[i]中。

照著這個總結很容易實現挖坑填數的程式碼:

int AdjustArray(int s[], int l, int r) //返回調整後基準數的位置
{
	int i = l, j = r;
	int x = s[l]; //s[l]即s[i]就是第一個坑
	while (i < j)
	{
		// 從右向左找小於x的數來填s[i]
		while(i < j && s[j] >= x) 
			j--;  
		if(i < j) 
		{
			s[i] = s[j]; //將s[j]填到s[i]中,s[j]就形成了一個新的坑
			i++;
		}

		// 從左向右找大於或等於x的數來填s[j]
		while(i < j && s[i] < x)
			i++;  
		if(i < j) 
		{
			s[j] = s[i]; //將s[i]填到s[j]中,s[i]就形成了一個新的坑
			j--;
		}
	}
	//退出時,i等於j。將x填到這個坑中。
	s[i] = x;

	return i;
}

 再寫分治法的程式碼:

void quick_sort1(int s[], int l, int r)
{
	if (l < r)
    {
		int i = AdjustArray(s, l, r);//先成挖坑填數法調整s[]
		quick_sort1(s, l, i - 1); // 遞迴呼叫 
		quick_sort1(s, i + 1, r);
	}
}

這樣的程式碼顯然不夠簡潔,對其組合整理下:

public static void QuichSort(int[] arr, int front, int rear)
{
    if (front < rear)//This line is important, as it's to terminate the iteration
    {
        int first = front, last = rear, key = arr[front];                      //\\
                                                                               // ||
        while(first<last)//必須此條件,用於在單次遍歷中確保交換兩邊所有大於和小於基準數的數// ||
        {                                                                      //這段
            while (first < last && arr[last] >= key)// 從右向左找第一個小於x的數   //程式碼
                last--;                                                        //都是
            arr[first] = arr[last];                                            //挖坑
                                                                               //填數
            while (first < last && arr[first] < key)//從左向右找第一個大於等於x的數 // ||
                first++;                                                       //||
            arr[last] = arr[first];                                            //||
        }                                                                      //||
        //當出了while,上面必然是迴圈了多次————是針對某個基準數迴圈多次!確保關於這個基準數兩邊有序
        //這個時候first和last已經相等了                                            //||
        arr[first] = key;                                                      ////

        //QuichSort(arr, front, first - 1); 這句和下面一句一樣,因為first等於last
        QuichSort(arr, front, last - 1); // 遞迴呼叫                            //這兩句
        QuichSort(arr, first + 1, rear);                                      //是分治
    }
}

快速排序還有很多改進版本,如隨機選擇基準數,區間內資料較少時直接用另的方法排序以減小遞迴深度。有興趣的筒子可以再深入的研究下。

注1,有的書上是以中間的數作為基準數的,要實現這個方便非常方便,直接將中間的數和第一個數進行交換就可以了。

-----------------------------------------圖片摘自百度百科------------------------


 這張圖片提供了另一種容易理解的方式:

每次選一個基準數,就相當於按照此基準數劃一條線,比這條線高的放左邊,比這條線高的放右邊。

具體怎麼放就結合上面的挖坑填充的思路,就更容易理解了。

相關推薦

白話經典系列 快速排序 快速

轉自:http://blog.csdn.net/morewindows/article/details/6684558 快速排序由於排序效率在同為O(N*logN)的幾種排序方法中效率較高,因此經常被採用,再加上快速排序思想----分治法也確實實用,因此很多軟體公司的筆試面試,包括像騰訊,微軟等知名IT公

白話經典系列 快速排序 快速

快速排序由於排序效率在同為O(N*logN)的幾種排序方法中效率較高,因此經常被採用,再加上快速排序思想----分治法也確實實用,因此很多軟體公司的筆試面試,包括像騰訊,微軟等知名IT公司都喜歡考這個,還有大大小的程式方面的考試如軟考,考研中也常常出現快速排序的身影。 總

白話經典系列 快速排序 快速

                 快速排序由於排序效率在同為O(N*logN)的幾種排序方法中效率較高,因此經常被採用,再加上快速排序思想----分治法也確實實用,因此很多軟體公司的筆試面試,包括像騰訊,微軟等知名IT公司都喜歡考這個,還有大大小的程式方面的考試如軟考,考研中也常常出現快速排序的身影。總的說來

白話經典系列五 歸併排序的實現(講的真好)

歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。 首先考慮下如何將將二個有序數列合併。這個非常簡單,只要從比較二個數列的第一個數,誰小就先取誰,取了後就在對應數列中刪除這個數。然後再

白話經典系列五 歸併排序的實現

 歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。首先考慮下如何將將二個有序數列合併。這個非常簡單,只要從比較二個數列

白話經典系列十一一道有趣的GOOGLE面試題 --解法2

                上一篇《白話經典算法系列之十一道有趣的GOOGLE面試題》中對一道有趣的GOOGLE面試題進行了詳細的講解,使用了類似於基數排序的做法在O(N)的時間複雜度和O(1)的空間複雜度完成了題目的要求,文章發表後,網友fengchaokobe在評論中給出了另一種解法,見下圖。文字版:

白話經典系列三 希爾排序的實現

希爾排序的實質就是分組插入排序,該方法又稱縮小增量排序,因DL.Shell於1959年提出而得名。   該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)

白話經典系列七 堆與堆排序

 堆排序與快速排序,歸併排序一樣都是時間複雜度為O(N*logN)的幾種常見排序方法。學習堆排序前,先講解下什麼是資料結構中的二叉堆。二叉堆的定義二叉堆是完全二叉樹或者是近似完全二叉樹。二叉堆滿足二個特

白話經典系列九 從歸併排序到數列的逆序數對(微軟筆試題)

首先來看看原題   微軟2010年筆試題 在一個排列中,如果一對數的前後位置與大小順序相反,即前面的數大於後面的數,那麼它們就稱為一個逆序數對。一個排列中逆序的總數就稱為這個排列的逆序數。如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序數對,因此整個陣列的逆序數對個數為

白話經典系列四 直接選擇排序及交換二個數據的正確實現

直接選擇排序和直接插入排序類似,都將資料分為有序區和無序區,所不同的是直接播放排序是將無序區的第一個元素直接插入到有序區以形成一個更大的有序區,而直接選擇排序是從無序區選一個最小的元素直接放到有序區的最後。 設陣列為a[0…n-1]。 1.      初始

Java學習---那些事

tco 那些事 www com 遊學 leetcode 更多 ava 今日頭條 【更多參考】 LeetCode算法 每日一題 1: Two Sum ----> 更多參考【今日頭條--松鼠遊學】 史上最全的五大算法總結【轉】Java學習---算法那些事

白話經典系列之一 氣泡排序的三種實現

                 氣泡排序是非常容易理解和實現,,以從小到大排序舉例:設陣列長度為N。1.比較相鄰的前後二個數據,如果前面資料大於後面的資料,就將二個數據交換。2.這樣對陣列的第0個數據到N-1個數據進行一次遍歷後,最大的一個數據就“沉”到陣列第N-1個位置。3.N=N-1,如果N不為0就重複

經典系列不死神兔

Fibonacci為1200年代的歐洲數學家,在他的著作中曾經提到:「若有一隻免子每個月生一隻小免子,一個月後小免子也開始生產。起初只有一隻免子,一個月後就有兩隻免子,二個月後有三隻免子,三個月後有五隻免子(小免子投入生產)......。 這就是著名的不死神兔問題,即Fib

圖解系列希爾排序

希爾排序 (1)演算法描述 對於給定的線性序列,將這個序列按照一定規則進行分組,每個小序列使用插入排序的方法排序。由於是每個分組進行排序,序列總體只是區域性有序,全域性還是無序的。因此全部的分組進行一次排序後,再將小分組劃分成更大的分組進行排序,直到只有一個分組,並且這個分組也是有序的。也就是說上面描述的

圖解系列歸並排序

功能 復雜 進入 都沒有 urn 分割 alt 圖解 個數 (1)算法描述 對於給定的線性序列,將當前序列不斷的進行分組,當每個分組的數據只有一個元素時,代表這個分組是有序的,那麽向上合並。每一層兩兩合並,合並的過程是,開辟一個新空間,使用兩個指針同時掃描兩個有序的分組,使

[閉目洞察系列二]插入排序三種實現

直接插入排序的思想是:將一個待排序的數字按照指定的排序規則,插入到一個前面已經排好序的的子序列中, 從而形成一個新的子序列,等待下一個數字的插入, 直至所有數字排序結束。 假設有陣列a[0 ... n - 1], 排序流程為: a[0]位第一個有序子序列, a[1 ..

排序系列直接插入排序

直接插入排序 1 基本原理 1 核心思想:插入排序通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入 ,如此重複,直至完成序列排序。 2 演算法分析:

系列枚舉生理周期

pac cin 輸出 peak 給定 cout 指定 eno d+ 題目     人有體力、情商、智商的高峰日子,它們分別每隔 23天、28天和33天出現一次。對於每個人,我們想 知道何時三個高峰落在同一天。給定三個高峰出現 的日子p,e和i(不一定是第一次高峰出

轉載K-NN 學習總結

數值 最簡 點距 情況 得到 tar cal 解決 定義域 1. K-NN算法簡介  K-NN算法 ( K Nearest Neighbor, K近鄰算法 ), 是機器學習中的一個經典算法, 比較簡單且容易理解. K-NN算法通過計算新數據與訓練數據特征值之間的距離, 然

Spring Boot幹貨系列:(三)啟動原理解析

無法 time exp 記得 started 打印 ping 正文 exclude 前言 前面幾章我們見識了SpringBoot為我們做的自動配置,確實方便快捷,但是對於新手來說,如果不大懂SpringBoot內部啟動原理,以後難免會吃虧。所以這次博主就跟你們一起一步步揭開