判斷各種排序算法的穩定性

排序算法穩定性

  如果在一個待排序的序列中,存在2個相等的數,在排序後這2個數的相對位置保持不變,那麼該排序算法是穩定的;否則是不穩定的。

舉個例子
  對五個同學(A,B,C,D,E)進行成績排序,他們的成績分別爲:90,88,79,88,92,按成績從高到低排(92,90,88,88,79):

E,A,B,D,C——穩定的(B,D的相對位置沒有變化)
E,A,D,B,C——不穩定的(B,D的相對位置發生了變化)

排序算法穩定的意義

排序算法如果是穩定的,從一個鍵上排序,然後在從另一個鍵上排序,第一個鍵排序的結果可以爲第二個鍵排序所用。

上面是引用別人的話,感覺不怎麼對。

舉個例子
  對五個同學(A,B,C,D,E)進行成績排序,他們語數外的總成績分別爲:271,282,264,295,282,同學B和E的總成績是一樣的,如果是穩定排序,按成績高低排就是:D,B,E,A,C(B,E的相對位置未改變),如果是不穩定排序就是:D,E,B,A,C(B,E的相對位置改變了),對於排序結果還可以用語文的單科成績來進行再排序。

如果排序算法是穩定的,元素交換的次數可能會少一點(沒有查到相關資料)

排序算法的穩定性判定

1.冒泡排序(Bubble Sort)

  冒泡排序就是把小的元素往前調或者把大的元素往後調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。所以,如果兩個元素相等,我想你是不會再無聊地把他們倆交換一下的;如果兩個相等的元素沒有相鄰,那麼即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前後順序並沒有改變,所以冒泡排序是一種穩定的排序算法。

2.選擇排序(Selection sort)

  選擇排序是給每個位置選擇當前元素最小的,比如給第一個位置選擇最小的,在剩餘元素裏面給第二個元素選擇第二小的,依次類推,直到第n - 1個元素,第n個元素不用選擇了,因爲只剩下它一個最大的元素了。那麼,在一趟選擇,如果當前元素比一個元素小,而該小的元素又出現在一個和當前元素相等的元素後面,那麼交換後穩定性就被破壞了。比較拗口,舉個例子,序列5 8 5 2 9,我們知道第一遍選擇第1個元素5會和2交換,那麼原序列中2個5的相對前後順序就被破壞了,所以選擇排序是不穩定的排序算法。

3.插入排序(Insertion sort )

  插入排序是在一個已經有序的小序列的基礎上,一次插入一個元素。當然,剛開始這個有序的小序列只有1個元素,就是第一個元素。比較是從有序序列的末尾開始,也就是想要插入的元素和已經有序的最大者開始比起,如果比它大則直接插入在其後面,否則一直往前找直到找到它該插入的位置。如果碰見一個和插入元素相等的,那麼插入元素把想插入的元素放在相等元素的後面。所以,相等元素的前後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,所以插入排序是穩定的
  下圖演示了對4個元素進行直接插入排序的過程,共需要(a),(b),(c)三次插入。
這裏寫圖片描述

4.快速排序(Quick sort)

  快速排序(Quick sort)是對冒泡排序的一種改進,採用的是分治的思想。快速排序簡單的說就是選擇一個基準(一般選第一個數),將比基準大的數放在一邊,小的數放到另一邊。對這個數的兩邊再遞歸上述方法。
  一趟快速排序的算法是:
1)設置兩個變量i、j,排序開始的時候:i=0,j=N-1;
2)以第一個數組元素作爲關鍵數據,賦值給key,即key=A[0];
3)從j開始向前搜索,即由後開始向前搜索(j–),找到第一個小於key的值A[j],將A[j]和A[i]互換;
4)從i開始向後搜索,即由前開始向後搜索(i++),找到第一個大於key的A[i],將A[i]和A[j]互換;
5)重複第3、4步,直到i=j; (3,4步中,沒找到符合條件的值,即3中A[j]不小於key,4中A[i]不大於key的時候改變j、i的值,使得j=j-1,i=i+1,直至找到爲止。找到符合條件的值,進行交換的時候i, j指針位置不變。另外,i==j這一過程一定正好是i+或j-完成的時候,此時令循環結束)。
  如:排列 66 13 51 76 81 26 57 69 23
  以66爲基準,升序排序的話,比66小的放左邊,比66大的放右邊, 類似這種情況 13 。。。 66。。。69

  具體快速排序的規則一般如下:
  從右邊開始查找比66小的數,找到的時候先等一下,再從左邊開始找比66大的數,將這兩個數借助66互換一下位置,繼續這個過程直到兩次查找過程碰頭。
  例子中:

        66  13  51  76  81  26  57  69  23
從右邊找到23比66小,互換
        23  13  51  76  81  26  57  69  66
從左邊找到76比66大,互換
        23  13  51  66  81  26  57  69  76
繼續從右邊找到57比66小,互換
        23  13  51  57  81  26  66  69  76
從左邊查找,81比66大,互換
        23  13  51  57  66  26  81  69  76
從右邊開始查找26比66小,互換
        23  13  51  57  26  66  81  69  76
此時66左邊的數都比66小,66右邊的數都比66大,完成一輪排序,對這個數的兩邊再遞歸上述方法。

  在中樞元素和a[j]交換的時候,很有可能把前面的元素的穩定性打亂,比如序列爲5 3 3 4 3 8 9 10 11,現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂,所以快速排序是一個不穩定的排序算法,不穩定發生在中樞元素和a[j] 交換的時刻。

5.歸併排序(MERGE-SORT)

  歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。
  如 設有數列{6,202,100,301,38,8,1}

初始狀態:6,202,100,301,38,81
第一次歸併後:{6,202},{100,301},{8,38},{1},比較次數:3;
第二次歸併後:{6,100,202,301}{1,8,38},比較次數:4;
第三次歸併後:{1,6,8,38,100,202,301},比較次數:4;
總的比較次數爲:3+4+4=11

  歸併排序是把序列遞歸地分成短序列,遞歸出口是短序列只有1個元素(認爲直接有序)或者2個序列(1次比較和交換),然後把各個有序的段序列合併成一個有序的長序列,不斷合併直到原序列全部排好序。可以發現,在1個或2個元素時,1個元素不會交換,2個元素如果大小相等也沒有人故意交換,這不會破壞穩定性。那麼,在短的有序序列合併的過程中,穩定是是否受到破壞?沒有,合併過程中我們可以保證如果兩個當前元素相等時,我們把處在前面的序列的元素保存在結果序列的前面,這樣就保證了穩定性。所以,歸併排序也是穩定的排序算法。

6.基數排序(radix sort)

  基數排序(radix sort)屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,基數排序是按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序,最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,所以其是穩定的排序算法。
  如有一串數值如下所示:
  73, 22, 93, 43, 55, 14, 28, 65, 39, 81
  首先根據個位數的數值,在遍歷數據時將它們各自分配到編號0至9的桶(個位數值與桶號一一對應)中。

  分配結果(邏輯想象)如下圖所示:
這裏寫圖片描述
  分配結束後。接下來將所有桶中所盛數據按照桶號由小到大(桶中由頂至底)依次重新收集串起來,得到如下仍然無序的數據序列:

81  22  73  93  43  14  55  65  28  39//個位是自然排序的

  接着,再進行一次分配,這次根據十位數值來分配(原理同上),分配結果(邏輯想象)如下圖所示:
這裏寫圖片描述
  分配結束後。接下來再將所有桶中所盛的數據(原理同上)依次重新收集串接起來,得到如下的數據序列:

14  22  28  39  43  55  65  73  81  93

  觀察可以看到,此時原無序數據序列已經排序完畢。如果排序的數據序列有三位數以上的數據,則重複進行以上的動作直至最高位數爲止。

7.希爾排序(shell sort)

  希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序算法的一種更高效的改進版本。希爾排序是不穩定排序算法。
  希爾排序的大體思路是:將一個未排序的序列分成多個組,然後在組內使用插入排序對組內序列排序。我們的分組方式是將每隔固定位置(增量)的元素分成一組。之後去調整這個間隔大小,重新分組,組內重新排序。直到分組的間隔爲1,也就是所有的元素分成一組,再進行一次插入排序,這樣就可以完成整個序列的排序過程。
希爾排序是按照不同步長對元素進行插入排序,當剛開始元素很無序的時候,步長最大,所以插入排序的元素個數很少,速度很快;當元素基本有序了,步長很小, 插入排序對於有序的序列效率很高。所以,希爾排序的時間複雜度會比O(n^2)好一些。由於多次插入排序,我們知道一次插入排序是穩定的,不會改變相同元素的相對順序,但在不同的插入排序過程中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂,所以shell排序是不穩定的
  例如,假設有這樣一組數[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我們以步長爲5開始進行排序,我們可以通過將這列表放在有5列的表中來更好地描述算法,這樣他們就應該看起來是這樣:

13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10

  然後我們對每列進行排序:

10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45

  將上述四行數字,依序接在一起時我們得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].這時10已經移至正確位置了,然後再以3爲步長進行排序:

10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45

  排序之後變爲:

10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94

  依序連接在一起得到:[10 14 13 25 23 33 27 25 59 39 65 73 45 94 82 94]
最後以1步長進行排序(此時就是簡單的插入排序了)。

8.堆排序(Heapsort sort)

  堆分爲大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在數組的非降序排序中,需要使用的就是大根堆,因爲根據大根堆的要求可知,最大的值一定在堆頂。
  既然是堆排序,自然需要先建立一個堆,而建堆的核心內容是調整堆,使二叉樹滿足堆的定義(每個節點的值都不大於其父節點的值)。調堆的過程應該從最後一個非葉子節點開始,假設有數組A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。那麼調堆的過程如下圖,數組下標從0開始,A[3] = 5開始。分別與左孩子和右孩子比較大小,如果A[3]最大,則不用調整,否則和孩子中的值最大的一個交換位置,在圖1中是A[7] > A[3] > A[8],所以A[3]與A[7]對換,從圖1.1轉到圖1.2。
這裏寫圖片描述
  我們知道堆的結構是節點i的孩子爲2 * i和2 * i + 1節點,大頂堆要求父節點大於等於其2個子節點,小頂堆要求父節點小於等於其2個子節點。在一個長爲n 的序列,堆排序的過程是從第n / 2開始和其子節點共3個值選擇最大(大頂堆)或者最小(小頂堆),這3個元素之間的選擇當然不會破壞穩定性。但當爲n / 2 - 1, n / 2 - 2, … 1這些個父節點選擇元素時,就會破壞穩定性。有可能第n / 2個父節點交換把後面一個元素交換過去了,而第n / 2 - 1個父節點把後面一個相同的元素沒 有交換,那麼這2個相同的元素之間的穩定性就被破壞了。所以,堆排序不是穩定的排序算法。

  綜上,得出結論: 選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法,而冒泡排序、插入排序、歸併排序和基數排序是穩定的排序算法

發佈了116 篇原創文章 · 獲贊 132 · 訪問量 45萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章