1. 程式人生 > >1019:線性排序演算法

1019:線性排序演算法

目錄

一、線性排序演算法介紹

二、桶排序(Bucket sort)

三、計數排序(Counting sort)

四、基數排序(Radix sort)


總結:桶排序、計數排序、基數排序

一、線性排序演算法介紹

1.線性排序演算法包括桶排序、計數排序、基數排序。

2.線性排序演算法的時間複雜度為O(n)。

3.此3種排序演算法都不涉及元素之間的比較操作,是非基於比較的排序演算法。

4.對排序資料的要求很苛刻,重點掌握此3種排序演算法的適用場景。

二、桶排序(Bucket sort)

1.演算法原理:

1)將要排序的資料分到幾個有序的桶裡,每個桶裡的資料再單獨進行快速排序。

2)桶內排完序之後,再把每個桶裡的資料按照順序依次取出,組成的序列就是有序的了。

2.使用條件

1)要排序的資料需要很容易就能劃分成m個桶,並且桶與桶之間有著天然的大小順序。

2)資料在各個桶之間分佈是均勻的。

3.適用場景

1)桶排序比較適合用在外部排序中。

2)外部排序就是資料儲存在外部磁碟且資料量大,但記憶體有限無法將整個資料全部載入到記憶體中。

4.應用案例

1)需求描述:

  1. 有10GB的訂單資料,需按訂單金額(假設金額都是正整數)進行排序
  2. 但記憶體有限,僅幾百MB

2)解決思路:

  1. 掃描一遍檔案,看訂單金額所處資料範圍,比如1元-10萬元,那麼就分100個桶。
  2. 第一個桶儲存金額1-1000元之內的訂單,第二個桶存1001-2000元之內的訂單,依次類推。
  3. 每個桶對應一個檔案,並按照金額範圍的大小順序編號命名(00,01,02,…,99)。
  4. 將100個小檔案依次放入記憶體並用快排排序。
  5. 所有檔案排好序後,只需按照檔案編號從小到大依次讀取每個小檔案並寫到大檔案中即可。

3)注意點:若單個檔案無法全部載入記憶體,則針對該檔案繼續按照前面的思路進行處理即可。

三、計數排序(Counting sort)

1.演算法原理

1)計數其實就是桶排序的一種特殊情況。

2)當要排序的n個數據所處範圍並不大時,比如最大值為k,則分成k個桶

3)每個桶內的資料值都是相同的,就省掉了桶內排序的時間。

2.應用案例

案例分析:

  1. 假設只有8個考生分數在0-5分之間,成績存於陣列A[8] = [2,5,3,0,2,3,0,3]。
  2. 使用大小為6的陣列C[6]表示桶,下標對應分數,即0,1,2,3,4,5。
  3. C[6]儲存的是考生人數,只需遍歷一邊考生分數,就可以得到C[6] = [2,0,2,3,0,1]。
  4. 對C[6]陣列順序求和則C[6]=[2,2,4,7,7,8],c[k]儲存的是小於等於分數k的考生個數。
  5. 陣列R[8] = [0,0,2,2,3,3,3,5]儲存考生名次。那麼如何得到R[8]的呢?
  6. 從後到前依次掃描陣列A,比如掃描到3時,可以從陣列C中取出下標為3的值7,也就是說,到目前為止,包括自己在內,分數小於等於3的考生有7個,也就是說3是陣列R的第7個元素(也就是陣列R中下標為6的位置)。當3放入陣列R後,小於等於3的元素就剩下6個了,相應的C[3]要減1變成6。
  7. 以此類推,當掃描到第二個分數為3的考生時,就會把它放入陣列R中第6個元素的位置(也就是下標為5的位置)。當掃描完陣列A後,陣列R內的資料就是按照分數從小到大排列的了。

程式碼實現:

// 計數排序,a 是陣列,n 是陣列大小。假設陣列中儲存的都是非負整數。
public void countingSort(int[] a, int n) {
  if (n <= 1) return;
  // 查詢陣列中資料的範圍
  int max = a[0];
  for (int i = 1; i < n; ++i) {
    if (max < a[i]) {
      max = a[i];
    }
  }
  int[] c = new int[max + 1]; // 申請一個計數陣列 c,下標大小 [0,max]
  for (int i = 0; i <= max; ++i) {
    c[i] = 0;
  }
  // 計算每個元素的個數,放入 c 中
  for (int i = 0; i < n; ++i) {
    c[a[i]]++;
  }
  // 依次累加
  for (int i = 1; i <= max; ++i) {
    c[i] = c[i-1] + c[i];
  }
  // 臨時陣列 r,儲存排序之後的結果
  int[] r = new int[n];
  // 計算排序的關鍵步驟,有點難理解
  for (int i = n - 1; i >= 0; --i) {
    int index = c[a[i]]-1;
    r[index] = a[i];
    c[a[i]]--;
  }
  // 將結果拷貝給 a 陣列
  for (int i = 0; i < n; ++i) {
    a[i] = r[i];
  }
}

3.使用條件

1)只能用在資料範圍不大的場景中,若資料範圍k比要排序的資料n大很多,就不適合用計數排序;

2)計數排序只能給非負整數排序,其他型別需要在不改變相對大小情況下,轉換為非負整數;

3)比如如果考試成績精確到小數後一位,就需要將所有分數乘以10,轉換為整數。

四、基數排序(Radix sort)

1.演算法原理(以排序10萬個手機號為例來說明)

1)比較兩個手機號碼a,b的大小,如果在前面幾位中a已經比b大了,那後面幾位就不用看了。

2)藉助穩定排序演算法的思想,可以先按照最後一位來排序手機號碼,然後再按照倒數第二位來重新排序,以此類推,最後按照第一個位重新排序。

3)經過11次排序後,手機號碼就變為有序的了。

4)每次排序有序資料範圍較小,可以使用桶排序或計數排序來完成。

2.使用條件

1)要求資料可以分割獨立的“位”來比較;

2)位之間由遞進關係,如果a資料的高位比b資料大,那麼剩下的地位就不用比較了;

3)每一位的資料範圍不能太大,要可以用線性排序,否則基數排序的時間複雜度無法做到O(n)。

五、思考

1.如何根據年齡給100萬用戶資料排序?

2.對D,a,F,B,c,A,z這幾個字串進行排序,要求將其中所有小寫字母都排在大寫字母前面,但是小寫字母內部和大寫字母內部不要求有序。比如經過排序後為a,c,z,D,F,B,A,這個如何實現呢?如果字串中處理大小寫,還有數字,將數字放在最前面,又該如何解決呢?