徹底理解各種排序演算法

2021-09-13 16:21:12 字數 3223 閱讀 7583

**:排序

1、氣泡排序(bubbler sort)

前面剛說了氣泡排序的壞話,但氣泡排序也有其優點,那就是好理解,穩定,再就是空間複雜度低,不需要額外開闢陣列元素的臨時儲存控制項,當然了,編寫起來也容易。

其演算法很簡單,就是比較陣列相鄰的兩個值,把大的像泡泡一樣「冒」到陣列後面去,一共要執行n的平方除以2這麼多次的比較和交換的操作(n為陣列元素),其複雜度為ο(n²),如圖:

2、直接插入排序(straight insertion sort)

冒泡法對於已經排好序的部分(上圖中,陣列顯示為白色底色的部分)是不再訪問的,插入排序卻要,因為它的方法就是從未排序的部分中取出乙個元素,插入到已經排好序的部分去,插入的位置我是從後往前找的,這樣可以使得如果陣列本身是有序(順序)的話,速度會非常之快,不過反過來,陣列本身是逆序的話,速度也就非常之慢了,如圖:

3、二分插入排序(binary insertion sort)

這是對直接插入排序的改進,由於已排好序的部分是有序的,所以我們就能使用二分查詢法確定我們的插入位置,而不是乙個個找,除了這點,它跟插入排序沒什麼區別,至於二分查詢法見我前面的文章(本系列文章的第四篇)。圖跟上圖沒什麼差別,差別在於插入位置的確定而已,效能卻能因此得到不少改善。(效能分析後面會提到)

4、直接選擇排序(straight selection sort)

這是我在學資料結構前,自己能夠想得出來的排序法,思路很簡單,用打擂台的方式,找出最大的乙個元素,和末尾的元素交換,然後再從頭開始,查詢第1個到第n-1個元素中最大的乙個,和第n-1個元素交換……其實差不多就是冒泡法的思想,但整個過程中需要移動的元素比冒泡法要少,因此效能是比冒泡法優秀的。看圖:

5、快速排序(quick sort)

快速排序是非常優秀的排序演算法,初學者可能覺得有點難理解,其實它是一種「分而治之」的思想,把大的拆分為小的,小的再拆分為更小的,所以你一會兒從**中就能很清楚地看到,用了遞迴。如圖:

其中要選擇乙個軸值,這個軸值在理想的情況下就是中軸,中軸起的作用就是讓其左邊的元素比它小,它右邊的元素不小於它。(我用了「不小於」而不是「大於」是考慮到元素數值會有重複的情況,在**中也能看出來,如果把「>=」運算子換成「>」,將會出問題)當然,如果中軸選得不好,選了個最大元素或者最小元素,那情況就比較糟糕,我選軸值的辦法是取出第乙個元素,中間的元素和最後乙個元素,然後從這三個元素中選中間值,這已經可以應付絕大多數情況。

6、改進型快速排序(improved quick sort)

快速排序的缺點是使用了遞迴,如果資料量很大,大量的遞迴呼叫會不會導致效能下降呢?我想應該會的,所以我打算作這麼種優化,考慮到資料量很小的情況下,直接選擇排序和快速排序的效能相差無幾,那當遞迴到子陣列元素數目小於30的時候,我就是用直接選擇排序,這樣會不會提高一點效能呢?我後面分析。排序過程可以參考前面兩個圖,我就不另外畫了。

7、桶排序(bucket sort)

這是迄今為止最快的一種排序法,其時間複雜度僅為ο(n),也就是線性複雜度!不可思議吧?但它是有條件的。舉個例子:一年的全國高考考生人數為500萬,分數使用標準分,最低100,最高900,沒有小數,你把這500萬元素的陣列排個序。我們抓住了這麼個非常特殊的條件,就能在毫秒級內完成這500萬的排序,那就是:最低100,最高900,沒有小數,那一共可出現的分數可能有多少種呢?一共有900-100+1=801,那麼多種,想想看,有沒有什麼「投機取巧」的辦法?方法就是建立801個「桶」,從頭到尾遍歷一次陣列,對不同的分數給不同的「桶」加料,比如有個考生考了500分,那麼就給500分的那個桶(下標為500-100)加1,完成後遍歷一下這個桶陣列,按照桶值,填充原陣列,100分的有1000人,於是從0填到999,都填1000,101分的有1200人,於是從1000到2019,都填入101……如圖:

很顯然,如果分數不是從100到900的整數,而是從0到2億,那就要分配2億個桶了,這是不可能的,所以桶排序有其侷限性,適合元素值集合並不大的情況。

8、基數排序(radix sort)

基數排序是對桶排序的一種改進,這種改進是讓「桶排序」適合於更大的元素值集合的情況,而不是提高效能。它的思想是這樣的,比如數值的集合是8位整數,我們很難建立一億個桶,於是我們先對這些數的個位進行類似桶排序的排序(下文且稱作「類桶排序」吧),然後再對這些數的十位進行類桶排序,再就是百位……一共做8次,當然,我說的是思路,實際上我們通常並不這麼幹,因為c++的位移運算速度是比較快,所以我們通常以「位元組」為單位進行桶排序。但下圖為了畫圖方便,我是以半位元組(4 bit)為單位進行類桶排序的,因為位元組為單位進行桶排得畫256個桶,有點難畫,如圖:

基數排序適合數值分布較廣的情況,但由於需要額外分配乙個跟原始陣列一樣大的暫存空間,它的處理也是有侷限性的,對於元素數量巨大的原始陣列而言,空間開銷較大。效能上由於要多次「類桶排序」,所以不如桶排序。但它的複雜度跟桶排序一樣,也是ο(n),雖然它用了多次迴圈,但卻沒有迴圈巢狀。

9、效能分析和總結

先不分析複雜度為ο(n)的演算法,因為速度太快,而且有些條件限制,我們先分析前六種演算法,即:冒泡,直接插入,二分插入,直接選擇,快速排序和改進型快速排序。

我的分析過程並不複雜,嘗試產生乙個隨機數陣列,數值範圍是0到7fff,這正好可以用c++的隨機函式rand()產生隨機數來填充陣列,然後嘗試不同長度的陣列,同一種長度的陣列嘗試10次,以此得出平均值,避免過多波動,最後用excel對結果進行分析,ok,上圖了。

最差的一眼就看出來了,是冒泡,直接插入和直接選擇旗鼓相當,但我更偏向於使用直接選擇,因為思路簡單,需要移動的元素相對較少,況且速度還稍微快一點呢,從圖中看,二分插入的速度比直接插入有了較大的提公升,但**稍微長了一點點。

令人感到比較意外的是快速排序,3萬點以內的快速排序所消耗的時間幾乎可以忽略不計,速度之快,令人振奮,而改進型快速排序的線跟快速排序重合,因此不畫出來。看來要對快速排序進行單獨分析,我加大了陣列元素的數目,從5萬到150萬,畫出下圖:

可以看到,即便到了150萬點,兩種快速排序也僅需差不多半秒鐘就完成了,實在快,改進型快速排序效能確實有微略提高,但並不明顯,從圖中也能看出來,是不是我設定的最小快速排序元素數目不太合適?但我嘗試了好幾個值都相差無幾。

最後看線性複雜度的排序,速度非常驚人,我從40萬測試到1200萬,結果如圖:

可見稍微調整下演算法,速度可以得到質的飛公升,而不是我們以前所認為的那樣:再快也不會比冒泡法快多少啊?

我最後製作一張表,比較一下這些排序法:

各種排序演算法的理解

一,直接插入排序 總體思路 位於表中後面的元素依次與表中前面的元素比較,若比之小,則還需繼續和更前面的元素比較,直至遇到乙個比它大的元素或者比較到第乙個元素 哨兵 了。先將第乙個元素視為有序,第二個元素與第乙個元素比較,若比第乙個元素小,則插入到第乙個元素之前。第三個元素依次與第二個元素 第乙個元素...

徹底理解堆排序

堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即a parent i a i 在陣列的非降序排序中,需要使用的就是大根堆,因為根據大根堆的要求可知,最大的值一定在堆頂。既然是堆排序,自然需要先建立乙個堆,而建堆的核心內容是調整堆,使二叉樹滿足堆的定義 每個節點的...

深入理解各種排序演算法

hahah 1 插入排序 這種排序的方法是,舉個例子 陣列 3,2,5,4,8,7,1 這裡我們預設用的是從小到大的排序 1 此時陣列為 3,2,5,4,8,7,1 選出第二項 2 與第一項 3 比較,交換位置,陣列變成 2,3,5,4,8,7,1 此輪比較結束 2 此時陣列為 2,3,5,4,8,...