回顧比較排序

轉載:http://blog.csdn.net/nomasp

相信閱讀過前面5篇博文的童鞋們已經發現了“在排序的最終結果中,各元素的次序依賴於它們之間的比較”。於是乎,這類排序算法被統稱爲”比較排序“。

比較排序是通過一個單一且抽象的比較運算(比如“小於等於”)讀取列表元素,而這個比較運算則決定了每兩個元素中哪一個應該先出現在最終的排序列表中。

聲明:下面通過在維基百科中找到的非常完美的圖示來介紹一系列比較排序。

插入排序

在該系列的【算法】1中我們便介紹了這個基本的算法,它的比較過程如下:

這裏寫圖片描述

以下是用插入排序對30個元素的數組進行排序的動畫:

這裏寫圖片描述

選擇排序

選擇排序的比較過程如下:

這裏寫圖片描述

其動畫效果如下:

這裏寫圖片描述

歸併排序

前面多次寫到歸併排序,它的比較過程如下:

這裏寫圖片描述

歸併排序的動畫如下:

這裏寫圖片描述

堆排序

在該系列的【算法】4中我們便介紹了快排,構建堆的過程如下:

這裏寫圖片描述

堆排序的動畫如下:

這裏寫圖片描述

快速排序

在該系列的【算法】5中我們便介紹了快排,它的比較過程如下:

這裏寫圖片描述

快速排序的動畫如下:

這裏寫圖片描述

另外一些比較排序

以下這些排序同樣也是比較排序,但該系列中之前並未提到。

Intro sort

該算法是一種混合排序算法,開始於快速排序,當遞歸深度超過基於正在排序的元素數目的水平時便切換到堆排序。它包含了這兩種算法優良的部分,它實際的性能相當於在典型數據集上的快速排序和在最壞情況下的堆排序。由於它使用了兩種比較排序,因而它也是一種比較排序。

冒泡排序

大家應該多少都聽過冒泡排序(也被稱爲下沉排序),它是一個非常基本的排序算法。反覆地比較相鄰的兩個元素並適當的互換它們,如果列表中已經沒有元素需要互換則表示該列表已經排好序了。(看到列表就想到半年前在學的Scheme,歡迎大家也去看看,我開了2個專欄來介紹它們)

上面的描述中已經體現了比較的過程,因而冒泡排序也是一個比較排序,較小的元素被稱爲“泡(Bubble)”,它將“浮”到列表的頂端。

儘管這個算法非常簡單,但大家應該也聽說了,它真的非常的慢。

冒泡排序的過程如下:

這裏寫圖片描述

冒泡排序的動畫演示:

這裏寫圖片描述

其最好情況、最壞情況的運行時間分別是:Θ(n)Θ(n2)

奇偶排序

奇偶排序和冒泡排序有很多類似的特點,它通過比較在列表中所有的單雙號索引的相鄰元素,如果有一對是錯誤排序(也就是前者比後者大),那麼將它們交換,之後不斷的重複這一步驟,直到整個列表排好序。

而鑑於此,它的最好情況、最壞情況的運行時間均和冒泡排序相同:Θ(n)Θ(n2)

奇偶排序的演示如下:

這裏寫圖片描述

下面是C++中奇偶排序的示例:

<code class="hljs d has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span> <<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> T>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> OddEvenSort (T a[], <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> n)
{
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> ; i < n ; i++)
    {
         <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (i & <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 'i' is odd</span>
         {
             <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> j = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span> ; j < n ; j += <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>)
             {     
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (a[j] < a[j-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>])
                      swap (a[j-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>], a[j]) ;
             }
          }
          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span>
          {  
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> j = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span> ; j < n ; j += <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>)
              {
                   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (a[j] < a[j-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>])
                       swap (a[j-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>], a[j]) ;
              } 
          }
    }
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li></ul>

雙向冒泡排序

雙向冒泡排序也被稱爲雞尾酒排序、雞尾酒調酒器排序、搖牀排序、漣漪排序、洗牌排序、班車排序等。(再多再華麗麗的名字也難以彌補它的低效)

和冒泡排序的區別在於它是在兩個方向上遍歷列表進行排序,雖然如此但並不能提高漸近性能,和插入排序比起來也沒太多優勢。

它的最好情況、最壞情況的運行時間均和冒泡排序相同:Θ(n)Θ(n2)

這裏寫圖片描述


排序算法的下界

我們可以將排序操作進行得多塊?

這取決於計算模型,模型簡單來說就是那些你被允許的操作。

決策樹

決策樹(decision tree)是一棵完全二叉樹,它可以表示在給定輸入規模情況下,其一特定排序算法對所有元素的比較操作。其中的控制、數據移動等其他操作都被忽略了。

這裏寫圖片描述

這是一棵作用於3個元素時的插入排序的決策樹。標記爲i:j的內部結點表示aiaj之間的比較。

由於它作用於3個元素,因此共有A33=6種可能的排列。也正因此,它並不具有一般性。

而對序列<a1=7,a2=2,a3=5>和序列<a1=5,a2=9,a3=6>進行排序時所做的決策已經由灰色和黑色粗箭頭指出了。

這裏寫圖片描述

決策樹排序的下界

如果決策樹是針對n個元素排序,那麼它的高度至少是nlgn

在最壞情況下,任何比較排序算法都需要做Ω(nlgn)次比較。

因爲輸入數據的Ann種可能的排列都是葉結點,所以Annl,由於在一棵高位h的二叉樹中,葉結點的數目不多於2h,所以有:

n!l2h

對兩邊取對數:

=> lg2hlgn!

=> lg2h=hlg2lgn!

又因爲:

lg2<1

所以:

nlgn!=Ω(nlgn)

因爲堆排序和歸併排序的運行時間上界均爲O(nlgn),因此它們都是漸近最優的比較排序算法。

線性時間排序

計數排序

計數排序(counting sort)的思路很簡單,就是確定比x小的數有多少個。加入有10個,那麼x就排在第11位。

嚴謹來講,在計算機科學中,計數排序是一個根據比較鍵值大小的排序算法,它也是一個整數排序算法。它通過比較對象的數值來操作,並通過這些計數來確定它們在即將輸出的序列中的位置。它的運行時間是線性的且取決於最大值和最小值之間的差異,當值的變化明顯大於數目時就不太適用了。而它也可以作爲基排序的子程序。

<code class="hljs vhdl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">COUNTING-SORT(A,B,k)
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>   let C[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.</span>..k] be a <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">array</span>
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> i=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> k
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>       C[i]=o
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4</span>   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> j=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> A.length
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">5</span>       C[A[j]]=C[A[j]]+<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">6</span>   // C[i] now contains the number <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> element equal <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> i.
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">7</span>   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> i=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> k
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>       C[i]=C[i]+C[i-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>]
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">9</span>   // C[i] now contains the number <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> element less than <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">or</span> equal <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> i.
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> j=A.length <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">downto</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11</span>      B[C[A[j]]]=A[j]
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">12</span>      C[A[j]]=C[A[j]]-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

第2-3步,C數組的元素被全部初始化爲0,此時耗費Θ(k)時間。

第4-5步,也許不太好想象,其實就是在C數組中來計數A數組中的數。比如說,A數組中元素”3”有4個,那麼C[3]=4。此時耗費Θ(n)時間。

第7-8步,也是不太好想象的計算,也就是說如果C[0]=1C[1]=4,那麼計算後的C[0]不變,C[1]=5。此時耗費Θ(k)時間。

第10-12步,把每個元素A[j]放到它在輸出數組B中的合適位置。比如此時的第一次循環,先找到A[8],然後找到C[A[8]]的值,此時C[A[8]]的意義就在於A[8]應在B數組中的位置。完成這一步後將C[A[8]]的值減一,因爲它只是一個計數器。這裏耗費的時間爲Θ(n)

這裏寫圖片描述

k=O(n)時,計數排序的運行時間爲Θ(n)

基數排序

基數排序(radix sort)是一個古老的算法,它用於卡片排序機上。說來也巧,寫這篇博客的前一天晚上還在書上看到這種機器,它有80列,每一列都有12個孔可以打。

它可以使用前面介紹的計數排序作爲子程序,然而它並不是原址排序;相比之下,很多運行時間爲Θ(nlgn)的比較排序卻是原址排序。因此當數據過大而內存不太夠時,使用它並不是一個明智的選擇。

這裏寫圖片描述

關鍵在於依次對從右往左每一列數進行排序,其他的列也相應移動。

桶排序

這倒是一個有趣的算法了,它充分利用了鏈表的思想。

桶排序(bucket sort)在平均情況下的運行時間爲O(n)

計數排序假設n個輸入元素中的每一個都在0和k之間,桶排序假設輸入數據是均勻分佈的,所以他們的速度都非常快。但並不能因爲這些是假設就說它們不實用不準確,真正的意義在於你可以根據情況選擇合適的算法。比如說,輸入的n個元素並不是均勻分佈的,但它們都在0到k之間,那麼就可以用計數排序。

說到桶,我想到的是裝滿葡萄酒的酒桶以及裝滿火藥的火藥桶。這裏是桶是指的算法將[0,1)區域劃分爲了n個相同大小的空間,它們被稱爲桶。

既然有了這個劃分,那麼就要用到它們。假設輸入的是n個元素的數組A,且對於所有的i都有0A[i]<1。你也許會覺得怎麼可能輸入的數組元素都湊巧滿足呢,當然不會這麼湊巧,但是你可以人爲地改造它們呀。比如<10,37,31,87>,你可以將它們都除以100,得到<0.10,0.37,0.31,0.87>

還需要一個臨時的數組B[0…n-1]來保存這些桶(也就是鏈表),而鏈表支持搜索,刪除和插入。關於鏈表的部分後面的博客中會有詳細介紹。

<code class="hljs livecodeserver has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">BUCKET-SORT(A)
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>   n=A.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">length</span>
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>   let B[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.</span>..n-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>] be <span class="hljs-operator" style="box-sizing: border-box;">a</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">new</span> array
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> i=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">to</span> n-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4</span>       make B[i] <span class="hljs-operator" style="box-sizing: border-box;">an</span> <span class="hljs-constant" style="box-sizing: border-box;">empty</span> list
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">5</span>   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> i=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">to</span> n
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">6</span>       insert A[i] <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">into</span> list B[小於等於nA[i]的最大整數]
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">7</span>   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> i=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">to</span> n-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>       <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sort</span> list B[i] <span class="hljs-operator" style="box-sizing: border-box;">with</span> insertion <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sort</span>
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">9</span>   concatenate <span class="hljs-operator" style="box-sizing: border-box;">the</span> lists B[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>],B[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>],...B[n-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>] together <span class="hljs-operator" style="box-sizing: border-box;">in</span> order</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>

這裏寫圖片描述

學習算法一定要體會到這種算法內每一步的改變,也要體會不同算法之間的演化和進步。在後面的鏈表中,我會更加側重於思路以及算法的進化


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