演算法系列(三) 快速排序

2021-06-08 02:14:25 字數 3329 閱讀 8420

快速排序

終於到我們人見人愛,花見花開,鳥見鳥呆,車見車爆胎

快排的好處不用多說,平均時間的nlogn

,o(1)

的輔助空間,一般比其他的排序演算法要快得多。

當然也有些不足,首先,不穩定,所以多關鍵字排序的最後一排肯定不能用了,另外,最壞情況下則為n^2

。快速排序和歸併排序一樣,都屬於分治的排序方法。據說當年hoare 

首次提出該演算法時,驚訝於對快排的效能及**的優美,最後還是狠狠地進行了完全的數學分析後,才發表快排**的。

所有的計算機科學家、程式設計師排列出心中的十大演算法時,快速排序總是會位列其中。可想而知快排的實用性加優美性。

和歸併一樣,它將陣列劃分成兩個部分,不同在於,歸併分別把兩個部分進行處理好了,再進行merge

。而快排則是在劃分時就進行處理,再分別對兩個部分進行排序。

劃分過程中,我們會重排陣列(使用swap

交換),得到這樣的乙個陣列:

1、我們選定陣列中某個元素作為pivot

,劃分後,該元素出現在i位。

2、a[1]-a[i-1]中的元素,都比

a[i]

小。3、a[i+1]-a[n]中的元素,都比

a[i]

大。所以,在quicksort

中,我們需要乙個呼叫乙個用於劃分的

partition

函式,在判斷該部分陣列元素個數

>1

後,則劃分,再遞迴處理劃分後的兩個部分。**如下:

void quicksort(int a, int l, int r)

}

下面是劃分的處理了。劃分才是快排中最重要的,就像歸併裡的merge

一樣。而劃分就我所知的,就有兩種不同的實現方法,乙個用

for,從頭掃到尾,乙個用

while

,兩頭往中間走。

本質上兩個方法肯定沒有區別,一般用的是while

的方法,但是,

for的**更顯優美,所以兩個都介紹下吧。

首先是流行的while

。這裡先不介紹隨機化的快排,我們直接選取該部分的第乙個元素作為

pivot

,也就是把

a[l]

從原位置中取出來,放到

pivot

,a[l]

已經為空。另外還知道最高位和最低位所處的位置

l 和 

r,就可以開始比較劃分過程。

此時我們把l 和 r

看作是已經劃分好的元素的分界線,

l 以左一定小於

pivot,r 

以右一定大於

pivot

,而且由於

c語言函式中形參的改變不影響實參,沒有必要使用額外的變數(給變數起名是件很痛苦的事)。

下面就看你的愛好是從頭到腳還是反過來,我這裡是從r到l

,將當前

a[r]

與pivot(

即初始的

a[l])

比較,若大於(等於看你心情),則符合要求,

r--,並繼續用

a[r]

與pivot

比較;否則,結束該迴圈,將

a[r]

的值(因為

),放到

a[l]

的位置中——

a[l]

原本是空的,則

a[r]

為空。

while (a[r] >= pivot) r--;

a[l] = a[r];

接著又從l到r

,相似的方法,比較後,或

l++,或

a[l]

的值放到

a[r]

。過程如圖:

如此迴圈下去,什麼時候結束呢?l和r

鵲橋相會時,此時,

l == r,l

左邊的項全小於

pivot,r

右邊的項全大於

pivot

,我們也就找到了

pivot

在劃分後的位置。

另外,我剛才專門試了一下,必須給上面的兩個while的迴圈條件加上 l < r,否則會出界。

於是,完整的partition while

版**為:

int partition(int a, int l, int r)

a[l] = pivot;

return l;

}

吐槽啊,這個編輯器偶爾給我抽一下的……

然後是for

版了。這回我改一改風格,先上**:

int partition(int a, int l, int h)

swap(l, i - 1);

return i - 1;

}

這個寫法最開始是從《程式設計實踐》裡面看來了(相近,但有改動),然後在上

coursera

的演算法分析與設計時,用

while

版快排連錯兩次以後,一看,原來是要這個寫法,印象瞬間深刻了。

相對不是那麼好理解,就像我之前面試時,就遇到不熟練的問題。現在我就來解釋清楚吧!

看圖說話最方便,所以,來張圖:

這裡的分法和while

不同,while

是中間未分,這裡是末尾未劃分。除了用個

pivot = a[l]

(圖中是

a[r]

)以外,還需要迴圈的

j,和表示

<=pivot 

和 >pivot

的分界線的

i(也有叫

last

的)。

從l+1

開始到r,用i

來表示第乙個

>pivot

的數的位置(原圖是最後乙個

<= pivot

的位置),所以,每個數

a[j]

都和pivot

比較,大於

pivot

的,自然是在

i的右邊,而小於

pivot

的話,就

a[j]

和a[i]

進行交換,再將

i右移一位。

全部走一遍之後,我們已經得到劃分好的兩塊。不過,pivot

還在a[l]

處不動呢,於是又和

a[i-1]

交換下,即完成整個劃分過程。

本來還打算上《程式設計實踐》裡的快排**的,看看沒太多差別,就算了。

JAVA演算法系列 快速排序

首先說一下什麼是快排,比冒泡效率要高,快排的基本思路是首先找到乙個基準元素,比如陣列中最左邊的那個位置,作為基準元素key,之後在最左邊和最右邊設立兩個哨兵,i和j 之後,開始按住左哨兵 i 讓右哨兵 j 往左走 j 找到比key小的元素後,按住右哨兵 j 開始讓左哨兵往右走 i 直到找到比key大...

演算法系列 排序演算法(四)快速排序

快速排序是通過兩個指標相互交換完成一次快速排序,類似於遞迴的二分排序,從交換上來講比較像冒泡 為什麼這麼說呢?不管是插入還是直接,都需要在移動之前遍歷元素 冒泡直接比較交換。公升序 資料 4251 376下標 0123 4561 指標1 4 指標2 6 指標1是用來二分的標準,指標2是被二分的元素,...

演算法系列 JavaScript快速排序思想實現

快速排序離不開遞迴的思想,你如果不了解遞迴,可以結合我另外一篇文章來學習 演算法入門之遞迴分而治之思想的實現 網上有有趣的動態圖來表示快速排序,但其實我們大部分程式設計師都是腦子不太好使那種,即使看了形象生動的動態圖,還是想不到具體實現思路。排序都有基本的步驟,快排也不例外,通常分為這麼幾步 1 選...