1. 程式人生 > >排序(上):冒泡排序、插入排序和選擇排序

排序(上):冒泡排序、插入排序和選擇排序

最壞情況 選擇排序 main 評價 先後 序列 emp 復雜度 基本思想

如何分析一個排序算法?

分析一個排序算法的三要素:排序算法的執行效率、排序算法的內存消耗以及排序算法的穩定性。

排序算法的執行效率

對於排序算法執行效率的分析,一般是從以下三個方面來衡量:

  1. 最好情況、最壞情況、平均情況時間復雜度
  2. 時間復雜度的系數、常數、低階
  3. 比較次數和交換(或移動)次數

第1、2點在之前的復雜度分析中我們已經講過了,第3點會在這一節以及接下來的章節中詳細講解。

這一節和下一節講的都是基於比較的排序算法。基於比較的排序算法的執行過程,會涉及兩種操作,一種是元素比較大小,另一種是元素交換或移動。所以,我們如果在分析排序算法的執行效率的時候,應該把比較次數和交換(或移動)次數也考慮進去。

排序算法的內存消耗

算法的內存消耗可以通過空間復雜度來衡量,排序算法也不例外。不過,針對排序算法的空間復雜度,我們還引入了一個新的概念,原地排序(Sorted in place)。原地排序算法,就是特指空間復雜度為 O(1)的排序算法。我們今天所要講的三種排序算法:冒泡排序、插入排序和選擇排序,都是原地排序算法。

排序算法的穩定性

僅僅靠執行效率和內存消耗來衡量排序算法的好壞是不夠的。針對排序算法,我們還有一個重要的度量指標,穩定性。這個概念是說,如果待排序的序列中存在值相等的元素,經過排序之後,相等元素之間原有的先後順序不變。

比如我們有一組數據 2,9,3,4,8,3,按照大小排序之後就是 2,3,3,4,8,9。

這組數據裏有兩個 3。經過某種排序算法排序之後,如果兩個 3 的前後順序沒有改變,那我們就把這種排序算法叫作穩定的排序算法;如果前後順序發生變化,那對應的排序算法就叫作不穩定的排序算法。

冒泡排序(Bubble Sort)

(1)基本思想

冒泡排序的基本思想就是:從無序序列頭部開始,進行兩兩比較,根據大小交換位置,直到最後將最大(小)的數據元素交換到了無序隊列的隊尾,從而成為有序序列的一部分;下一次繼續這個過程,直到所有數據元素都排好序。

算法的核心在於每次通過兩兩比較交換位置,選出剩余無序序列裏最大(小)的數據元素放到隊尾。

(2)圖片示例

技術分享圖片

(以上兩點參考鏈接:https://blog.csdn.net/guoweimelon/article/details/50902597)

(3)代碼示例

 1 // 冒泡排序,a 表示數組,n 表示數組大小
 2 public void bubbleSort(int[] a, int n) {
 3   if (n <= 1) {
 4     return;
 5     }
 6  
 7  for (int i = 0; i < n; ++i) {
 8     // 提前退出冒泡循環的標誌位
 9     boolean flag = false;
10     for (int j = 0; j < n - i - 1; ++j) {
11       if (a[j] > a[j+1]) { // 交換
12         int tmp = a[j];
13         a[j] = a[j+1];
14         a[j+1] = tmp;
15         flag = true;  // 表示有數據交換      
16       }
17     }
18     if (!flag) break;  // 沒有數據交換,提前退出
19   }
20 }

結合上文提到的分析排序算法的三要素,我們可以得出以下結論:

  • 冒泡排序是原地排序算法:因為冒泡的過程只涉及相鄰數據的交換操作,只需要常量級的臨時空間,所以它的空間復雜度為 O(1),是一個原地排序算法。
  • 冒泡排序是穩定的排序算法:在冒泡排序中,只有當相鄰兩個元素大小不相等的時候,我們才做交換,相同大小的數據在排序前後不會改變順序,所以冒泡排序是穩定的排序算法。
  • 冒泡排序的時間復雜度是 O(n^2):冒泡排序在要排序的數據都是有序的情況下,我們只需要進行一次冒泡排序就可以結束了,所以最好情況時間復雜度為 O(n)。而在要排序的數據剛好是倒序排列的情況下,則需要進行 n 次冒泡操作,所以最壞情況時間復雜度為 O(n^2)。

插入排序(Insertion Sort)

(1)基本思想

插入排序是一種簡單直觀的排序算法。它的基本思想是通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。

(2)圖片示例

技術分享圖片

(圖片來自“極客時間”:https://time.geekbang.org/column/article/41802)

(3)代碼示例

 1 // 插入排序,a 表示數組,n 表示數組大小
 2 public void insertionSort(int[] a, int n) {
 3   if (n <= 1) {
 4     return;
 5     }
 6 
 7   for (int i = 1; i < n; ++i) {
 8     int value = a[i];
 9     int j = i - 1;
10     // 查找插入的位置
11     for (; j >= 0; --j) {
12       if (a[j] > value) {
13         a[j+1] = a[j];  // 數據移動
14       } else {
15         break;
16       }
17     }
18     a[j+1] = value; // 插入數據
19   }
20 }

同樣,結合上文提到的分析排序算法的三要素,我們可以得出以下結論:

  • 插入排序是原地排序算法:因為插入排序算法的運行並不需要額外的存儲空間,所以它的空間復雜度為 O(1),是一個原地排序算法。
  • 插入排序是穩定的排序算法:在插入排序中,對於值相同的元素,我們可以選擇將後面出現的元素,插入到前面出現元素的後面,這樣就可以保持原有的前後順序不變,所以插入排序是穩定的排序算法。
  • 插入排序的時間復雜度是 O(n^2):插入排序在要排序的數據都是有序的情況下,我們只需要比較一個數據就能確定插入的位置,所以最好情況時間復雜度為 O(n)。而在要排序的數據剛好是倒序排列的情況下,每次插入都相當於在數組的第一個位置插入新的數據,所以需要移動大量的數據,所以最壞情況時間復雜度為 O(n^2)。

選擇排序(Selection Sort)

(1)基本思想

選擇排序算法的實現思路有點類似插入排序,也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾。

(2)圖片示例

技術分享圖片

(圖片來自“極客時間”:https://time.geekbang.org/column/article/41802)

(3)代碼示例

 1 //選擇排序
 2 public class SelectionSort {
 3     public static void main(String[] args) {
 4         int[] arr={1,3,2,45,65,33,12};
 5         System.out.println("交換之前:");
 6         for(int num:arr){
 7             System.out.print(num+" ");
 8         }        
 9         //選擇排序的優化
10         for(int i = 0; i < arr.length - 1; i++) {// 做第i趟排序
11             int k = i;
12             for(int j = k + 1; j < arr.length; j++){// 選最小的記錄
13                 if(arr[j] < arr[k]){ 
14                     k = j; //記下目前找到的最小值所在的位置
15                 }
16             }
17             //在內層循環結束,也就是找到本輪循環的最小的數以後,再進行交換
18             if(i != k){  //交換a[i]和a[k]
19                 int temp = arr[i];
20                 arr[i] = arr[k];
21                 arr[k] = temp;
22             }    
23         }
24         System.out.println();
25         System.out.println("交換後:");
26         for(int num:arr){
27             System.out.print(num+" ");
28         }
29     }
30 }

選擇排序空間復雜度也是 O(1),是一種原地排序算法。它的最好情況時間復雜度、最壞情況和平均情況時間復雜度都為 O(n^2)。

選擇排序不是穩定的排序算法,因為它每次都要找出剩余未排序元素中的最小值,並和前面的元素交換位置,這樣就破壞了穩定性。

內容小結

  • 要想分析、評價一個排序算法,需要從執行效率、內存消耗和穩定性三個方面來看。
  • 插入排序優於冒泡排序,冒泡排序優於選擇排序。

排序(上):冒泡排序、插入排序和選擇排序