【DS】排序算法的穩定性


主要的排序算法有八種:直接插入排序,希爾排序(這兩種統稱為插入排序),冒泡排序,快速排序(這兩種統稱為交換排序),直接選擇排序,堆排序(這兩種統稱為選擇排序),歸並排序,基數排序。今天我們就討論一下它們各自的穩定性。如果對算法不熟悉,可以查看我的另外幾篇博客,然后再來閱讀。

 

一、什么是算法穩定性

考察排序算法的時候有一個很重要的特性,就是算法的穩定性:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,則稱這種排序算法是穩定的;否則稱為不穩定的。

 

二、算法穩定性的重要性

算法穩定性為什么這么重要呢?

1)在實際的應用中,我們交換的不一定只是一個整數,而可能是一個很大的對象,交換元素存在一定的開銷;

2)參照基數排序(后面會講),不穩定排序是無法完成基數排序的,講述完基數排序后,還會補充這里的原因。

 

三、八大算法的穩定性

1)直接插入排序@排序算法之插入排序(Insertion Sort)

其大致原理是:將數組分為無序區和有序區兩個區,然后不斷將無序區的第一個元素按大小順序插入到有序區中去,最終將所有無序區元素都移動到有序區完成排序。

我們假設一個數組,元素已經排序為{1,5A,7,5B,9},其中前面三個已經排序完成,后面沒有排序,即前面三個是有序區,后面兩個是無序區,現在要將無序區的5B插入到有序區,則如果我們將元素插入到5A之前,我們需要往后移動兩個元素,如果插入到5A之后,則需要移動一個元素,因此我們選擇移動一個元素,而5A和5B也保持原來的順序,因而直接插入排序是穩定的。

 

2)希爾排序@排序算法之希爾排序(Shell Sort)

其大致原理是:又稱Gap縮小排序。先將序列按Gap划分為元素個數相同的若干組,使用直接插入排序法進行排序,然后不斷縮小Gap直至為1,最后使用直接插入排序完成排序。希爾排序其實是直接插入排序的增強版。

我們來證明它是不穩定的,假設有一個數組{3,2A,2B,4},我們要升序排列,按照算法,第一次Gap=2,即可以分為{3,2B}和{2A,4}兩組,然后對每一組進行插入排序,可以排序成{2B,2A,3,4},第二次Gap=1,由於插入排序是穩定的,所以2A和2B不會交換順序了。由此可以看到,希爾排序是不穩定的。

 

3)冒泡排序@排序算法之冒泡排序(Bubble Sort)

其大致原理是:將序列划分為無序和有序區,不斷通過交換較大元素至無序區尾完成排序。

熟悉冒泡排序的人一定知道,冒泡排序通過不斷的交換元素,將無序區的最大(最小)元素往無序區搬運,因而和插入排序一樣,為了減少其交換次數,冒泡排序是穩定的。

 

4)快速排序@排序算法之快速排序(Quick Sort)

其大致原理是:不斷尋找一個序列的中點,將小於該中點的元素搬移到中點左邊,大於該中點的元素搬移到中點右邊,或者反過來。然后對中點左右的序列遞歸的進行排序,直至全部序列排序完成,使用了分治的思想。

關於算法的穩定性有一點本來是打算后面再講的,但是講到快速排序就一定要說了。讀者肯定注意到了,前面的插入排序和冒泡排序完全可以實現為不穩定算法,只是在比較元素決定是否交換的時候,是否加上等於號而已。快速排序更加顯示了這一點,解釋如下:

在算法導論里面,快速排序選擇都是元素序列的最后一個元素,假設元素序列如下{3,9,5A,6,8,5B},這種情況下,和上面的情況一下,穩不穩定還是看判斷的時候是否出現等號,但是如果選擇不是這樣的,我們假設一種特殊狀況:{3,9,5A,5B,6,8,5C},算法的實現是選擇中間的5B作為中點,則不論等號與否,都是不穩定的。實際上,算法導論的選擇是非常有意義的,了解其算法過程的人可以看到,這樣的選擇極大的降低了交換元素的復雜度和移動元素的次數。算法導論中是加了等號的,即≤最后一個元素的值被移到了左邊,因而快速排序是穩定的。

 

5)直接選擇排序@排序算法之選擇排序(Selection Sort)

其大致原理是:將序列划分為無序和有序區,尋找無序區中的最小值和無序區的首元素交換,有序區擴大一個,循環最終完成全部排序。

我們還是假設一個序列{1,3,5,10A,10B,7},看這個數列,假設前面三個是有序區,后面三個是無序區,則無序區中最小的元素是7,和無序區的首元素交換10A交換,則可以看到序列變成了{1,3,5,7,10B,10A},然后繼續,無序區就剩下{10B,10A},我們又可以看到,這里又是一個等號問題,同樣,前面的交換是必然的,而后面的交換(如果等於也要交換)則不是必然的,為了減少元素交換,直接選擇排序是不穩定的。

 

6)堆排序

其大致原理是:利用大根堆或小根堆思想,首先建立堆,然后將堆首與堆尾交換,堆尾之后為有序區。

考慮序列{9,5A,7,5B},按照堆排序的算法走一遍(算法導論中用的是最大堆,這個序列也是用最大堆來設計的),很快就可以發現,輸出序列為{5B,5A,7,9},而且與等號無關,因此堆排序是不穩定的。

 

7)歸並排序@排序算法之歸並排序(Merge Sort)

其大致原理是:將原序列划分為有序的兩個序列,然后利用歸並算法進行合並,合並之后即為有序序列。

歸並排序一樣是穩定的,但是歸並排序的穩定性並不是為了減少元素交換次數,因為它的算法實現中沒有元素交換這一概念。

 

8)基數排序

其大致原理是:將數字按位數划分出n個關鍵字,每次針對一個關鍵字進行排序,然后針對排序后的序列進行下一個關鍵字的排序,循環至所有關鍵字都使用過則排序完成。具體請參見:算法總結系列之五: 基數排序(Radix Sort)

基數排序對多個關鍵字進行排序,並且這些關鍵字還是有優先級別的,對於整數來說,位數越高的數字優先級越高,而基數排序則是對優先級低的先排序,因此,基數排序對於整數是從個十百千萬一個個去排序的。注意,這里必須使用穩定排序,否則,就會讓原先的地位排序成果毀於一旦,最終的不到正確的排序結果。

基數排序不過是一種思想,其每一位的排序都需要穩定算法,否則無法得到正確的結果。

 

三、總結

算法穩定性到底為什么如此重要?上面提到的八種算法可以看到,其實很多算法都是可以實現穩定和不穩定兩種情形的,那為什么選擇穩定?一個基本原因就是減少元素交換次數,但是也有像歸並排序這樣的算法,與交換無關,那么穩定算法的意義在哪里呢?

穩定算法在單次排序的時候,意義並不顯著,雖然上面提到減少元素交換,其實鏈表是可以避免這個消耗的,只不過操作比較復雜,其意義顯示在基數排序中,即,我們要對多個關鍵詞多次排序,這個時候,就一定要使用穩定算法。舉一個現實的例子,比如排序的對象是人名,假設有以下兩個人名:

Smith, Alfred
Smith, Zed

我們先按first name排序,再按照last name排序,按照first name排序完成以后,就是上面的樣子,再去按照last name排序,如果算法不穩定,則順序極就會顛倒,是不是?這里的last name和first name完全可以抽象成基數排序的不同位,不是穩定算法,就不能得到正確結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM