1. 程式人生 > >資料結構與演算法學習筆記之如何分析一個排序演算法?

資料結構與演算法學習筆記之如何分析一個排序演算法?

前言

現在IT這塊找工作,不會幾個演算法都不好意思出門,排序演算法恰巧是其中最簡單的,我接觸的第一個演算法就是它,但是你知道怎麼分析一個排序演算法麼?有很多時間複雜度相同的排序演算法,在實際編碼中,那又如何選擇呢?下面我們帶著問題一起學習一下。

 正文

一、常見經典的排序方法

(圖片來自於一畫素

插入排序

希爾排序(遞減增量排序演算法)

歸併排序

快速排序

 

氣泡排序

選擇排序

計數排序

計數排序

堆排序

二、 按照時間複雜度歸類

時間複雜度O(n2): 氣泡排序、插入排序、選擇排序  時間複雜度O(nlogn):

快速排序、歸併排序 時間複雜度O(n): 計數排序、基數排序、桶排序 

三、如何分析一個“排序演算法”?

從三個方面入手

a、演算法的執行效率

1.最好、最壞、平均情況時間複雜度。 從演算法的核心,複雜度入手,給出最好最壞,平均情況下的時間複雜度,便於分析
2. 時間複雜度的係數、常數和低階。 時間複雜度表示的是規模很大的一種增漲趨勢,很容易就忽略係數,低階,常數等,實際開發中排序的規模都是像10.100.1000這種小規模
3. 比較次數,交換(或移動)次數。 排序演算法執行過程中,涉及兩種操作,一種是元素比較大小,一種是元素交換或移動位置,所以比較次數,交換次數都得考慮進去。

b、排序演算法的記憶體消耗

演算法消耗可以通過空間複雜度來衡量

原地排序演算法:特指空間複雜度是O(1)的排序演算法。

c、排序演算法的穩定性

1. 穩定性概念:如果待排序的序列中存在值相等的元素,經過排序之後,相等元素之間原有的先後順序不變。
2. 穩定性重要性:可針對物件的多種屬性進行有優先順序的排序。
3. 舉例:給電商交易系統中的“訂單”排序,按照金額大小對訂單資料排序,對於相同金額的訂單以下單時間早晚排序。用穩定排序演算法可簡潔地解決。先按照下單時間給訂單排序,排序完成後用穩定排序演算法按照訂單金額重新排序。


四、詳解氣泡排序

氣泡排序只會操作相鄰的兩個資料。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關係要求,如果不滿足就讓它倆互換。
氣泡排序只涉及相鄰資料的交換,只需要常量級的臨時空間,所以它的空間複雜度未O(1)是原地排序演算法 穩定性:當有相鄰的兩個元素大小相等時,不做交換,氣泡排序是穩定的排序演算法。 引入兩個概念:
預設從小到大未有序
有序度:陣列中具有有序關係的元素對的個數。 滿有序度:完全有序的陣列 逆序度:陣列中具有無序關係的元素對的個數。
逆序度=滿有序度-有序度 排序的過程實際上就是增加有序度,減少逆序度的過程
時間複雜度:
1. 最好情況(滿有序度):O(n)。
2. 最壞情況(滿逆序度):O(n^2)。
3. 平均情況:
“有序度”和“逆序度”:對於一個不完全有序的陣列,如4,5,6,3,2,1,有序元素對為3個(4,5),(4,6),(5,6),有序度為3,逆序度為12;對於一個完全有序的陣列,如1,2,3,4,5,6,有序度就是n*(n-1)/2,也就是15,稱作滿有序度;逆序度=滿有序度-有序度;氣泡排序、插入排序交換(或移動)次數=逆序度。
最好情況下初始有序度為n*(n-1)/2,最壞情況下初始有序度為0,則平均初始有序度為n*(n-1)/4,即交換次數為n*(n-1)/4,因交換次數<比較次數<最壞情況時間複雜度,所以平均時間複雜度為O(n2)。 程式碼實現:
// 氣泡排序,a 表示陣列,n 表示陣列大小
public void bubbleSort(int[] a, int n) {
  if (n <= 1) return;
 
 for (int i = 0; i < n; ++i) {
    // 提前退出冒泡迴圈的標誌位
    boolean flag = false;
    for (int j = 0; j < n - i - 1; ++j) {
      if (a[j] > a[j+1]) { // 交換
        int tmp = a[j];
        a[j] = a[j+1];
        a[j+1] = tmp;
        flag = true;  // 表示有資料交換      
      }
    }
    if (!flag) break;  // 沒有資料交換,提前退出
  }
}

五、詳解插入排序

將資料分為兩個區間,已排序區間和未排序區間,初始已排序區間只有一個元素(即第一個資料),我們取未排序區間的元素,在已排序的區間中找到合適的位置插入位置插入,並保證已排序區間資料一直有序,重複過程,直到未排序區間中沒有元素

執行過程中看得出來,不需要額外的儲存空間,所以空間複雜度為0(1),也是原地排序演算法

同樣值的元素,前後順序保持不變,是穩定的排序演算法

時間複雜度:

最好時間複雜度為O(n)

最壞時間複雜度為O(n2)

平均時間複雜度為O(n2)

程式碼實現:

// 插入排序,a 表示陣列,n 表示陣列大小
public void insertionSort(int[] a, int n) {
  if (n <= 1) return;

  for (int i = 1; i < n; ++i) {
    int value = a[i];
    int j = i - 1;
    // 查詢插入的位置
    for (; j >= 0; --j) {
      if (a[j] > value) {
        a[j+1] = a[j];  // 資料移動
      } else {
        break;
      }
    }
    a[j+1] = value; // 插入資料
  }
}

六、詳解選擇排序

選擇排序將陣列資料分成已排序區間和未排序區間。初始已排序區間只有一個元素,即陣列第一個元素。在未排序區間找到最小的資料,將其放在已排序區間的末尾
空間複雜度為O(1),選擇排序是原地排序演算法。 未排序區間的元素和已排序區間的元素相同時,它可以放在已排序區間相同值的前或後,所以為不穩定的排序
時間複雜度:
1. 最好情況:O(n2)。
2. 最壞情況:O(n2)。
3. 平均情況:O(n2)(往陣列中插入一個數的平均時間複雜度是O(n),一共重複n次)。

七、各種排序方法的彙總比較


八、選擇排序和插入排序的時間複雜度相同,都是O(n^2),在實際的軟體開發中,為什麼我們更傾向於使用插入排序而不是氣泡排序演算法呢?

答:它們的元素比較次數以及交換元素的次數都是原始資料的逆序度,是一個固定值,但是從程式碼實現上來看,氣泡排序的資料交換要比插入排序的資料移動要複雜,氣泡排序需要3個賦值操作,而插入排序只需要1個,他們 的時間複雜度上都是O(n2),但是為了追求極致的效能,所以首選插入排序演算法

結尾

大家不妨試著分析一下其他的幾種演算法。

看再多遍都不如寫一篇來得深刻,建議大家多敲。

相關文章

以上內容為個人的學習筆記,僅作為學習交流之用。

 

歡迎大家關注公眾號,不定時乾貨,只做有價值的輸出

版權:本文版權歸作者
轉載:歡迎轉載,但未經作者同意,必須保留此段宣告;必須在文章中給出原文連線;否則必究法律責任