1. 程式人生 > >七、排序(3)——線性排序

七、排序(3)——線性排序

時間複雜度是 O(n) 的排序演算法:桶排序、計數排序、基數排序,也稱線性排序,他們都不是基於比較的排序演算法。

一、桶排序

1、基本思想

將要排序的資料分到幾個有序的桶裡,每個桶裡的資料再單獨進行進行排序。桶內排完序之後,再把每個桶裡的資料按照順序依次取出,從而形成有序序列。
在這裡插入圖片描述

2、桶排序對要排序資料的要求非常苛刻

  • 要排序的資料需要很容易就能劃分成 m 個桶,並且,桶與桶之間有著天然的大小順序。 ==》每個桶內的資料都排序完之後,桶與桶之間的資料不需要再進行排序。
  • 資料在各個桶之間的分佈是比較均勻的。==》若資料不均勻 ==》時間複雜度就將退化為 O(nlogn) 的排序演算法。

3、應用

適用於 外部排序。外部排序:資料儲存在外部磁碟中,資料量比較大,記憶體有限,無法將資料全部載入到記憶體中。

示例:有 10GB 的訂單資料,我們希望按訂單金額(假設金額都是正整數)進行排序,但是我們的記憶體有限,只有幾百 MB,沒辦法一次性把 10GB 的資料都載入到記憶體中。這個時候該怎麼辦呢?

思路:

  • 先掃描一遍檔案,看訂單金額所處的資料範圍。==》1元 ~ 10萬元
  • 將所有訂單根據金額劃分到 100 個桶裡,第一個桶我們儲存金額在 1 元到 1000 元之內的訂單,第二桶儲存金額在 1001 元到 2000 元之內的訂單,以此類推。每一個桶對應一個檔案,並且按照金額範圍的大小順序編號命名(00,01,02…99)
    • 理想情況
      • 訂單金額在 1 到 10 萬之間均勻分佈,那訂單會被均勻劃分到 100 個檔案中,每個小檔案中儲存大約 100MB 的訂單資料
      • ==》可以將這 100 個小檔案依次放到記憶體中,用快排來排序。
      • ==》 等所有檔案都排好序之後,我們只需要按照檔案編號,從小到大依次讀取每個小檔案中的訂單資料,並將其寫入到一個檔案中,那這個檔案中儲存的就是按照金額從小到大排序的訂單資料了。
    • 實際情況:不均勻分佈 ==》針對這些劃分之後還是比較大的檔案,繼續劃分。==》直到所有檔案都能讀入記憶體位置。
      • eg:訂單金額在 1 元到 1000 元之間的比較多,我們就將這個區間繼續劃分為 10 個小區間,1 元到 100元,101 元到 200 元,201 元到 300 元…901 元到 1000 元。

4、實現

#include <iostream>
using namespace std;
 
void BucketSort(int *A, int Max, int Size){
    int B[Max+1];
    int i,j,count = 0;
    memset(B, 0, (Max+1)*sizeof(int));
    for (i = 0; i < Size; i++) {
        j = A[i];
        B[j] += 1;
    }
    for (i = 0; i <= Max; i++) {
        if (B[i] > 0) {
            for (j = 0; j < B[i]; j++) {
                A[count] = i;
                count++;
            }
        }
    }
}
 
int main(int argc, const char * argv[])
{
    int A[]={1, 2, 2, 7, 4, 9, 3, 5};
    // 這裡可以用一個O(n)的函式來實現,假如陣列的手動輸入的,則在輸入的時候就可以獲取到。
    // 這裡直接賦值是為了排出別的因素,看著簡單。
    int Max = 9; 
    int Size = sizeof(A)/sizeof(int);
    BucketSort(A, Max, Size);
    for (int i = 0; i < Size; i++) {
        printf("%d ",A[i]);
    }
    printf("\n");
    return 0;
}

二、計數排序(Counting Sort)

1、基本思想

基本思路:通過對待排序序列中的每種元素的個數進行計數,然後獲得每個元素在排序後的位置的資訊的排序演算法。

桶排序的特殊情況:桶的大小粒度不一樣,每個桶內的資料值都是相同,省去了桶內排序的時間

2、適用性

只適用於在資料範圍不大的場景中,而且只能給非負整數排序或在不改變相對大小的情況下可轉化為非負整數

3、實現

#include <iostream>
#include <vector>
using namespace std;
 
void CountSort(vector<int> &arr, int maxVal) {
	int len = arr.size();
	if (len < 1)
		return;
	vector<int> count(maxVal+1, 0);
	vector<int> tmp(arr);
	for (auto x : arr)
		count[x]++;
	for (int i = 1; i <= maxVal; ++i)
		count[i] += count[i - 1];
	for (int i = len - 1; i >= 0; --i) {
		arr[count[tmp[i]] - 1] = tmp[i];
		count[tmp[i]]--;				//注意這裡要減1
	}
}
 
int main()
{
	vector<int> arr = { 1,5,3,7,6,2,8,9,4,3,3 };
	int maxVal = 9;
	CountSort(arr,maxVal);
	for (auto x : arr)
		cout << x << " ";
	cout << endl;
	return 0;
}

三、基數排序(Radix Sort)

1、基本思想

基數排序是桶排序的擴充套件。

基本思想:將整數按位數切割成不同的數字,然後按每個位數分別比較。

具體做法:將所有待比較數值統一為同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。

2、不同的情形

  • 數字的排序中,若存在不同長度的問題 ==》在位數短的前面補零
    在這裡插入圖片描述
  • 字串的排序,若存在不同長度的問題 ==》位數不夠的可以在後面補“0”
    • 根據ASCII 值,所有字母都大於“0”,所以補“0”不會影響到原有的大小順序。
      在這裡插入圖片描述
  • 其他含義序列中,若存在不同長度的問題 ==》具體情況具體分析