【排序五】非比較排序(計數排序&&基數排序)
比較排序:
一、計數排序
1、基本思想
給定一組要排序的序列,找出這組序列中的最大值,然後開闢一個最大值加1大小的陣列,將這個數組裡面的元素全部置零,然後用這個陣列統計出要排序的序列中各個元素出現的次數。等到統計完成的時候,排序就已經完成了。 圖解(假設升序):2、演算法執行步驟:
1>找出待排序序列裡的最大值max;
2>開闢一個大小為max+1的臨時陣列tmp ,將陣列元素的所有初值賦為0;
3>遍歷待排序序列,將序列中出現值作為下標,對tmp 陣列的對應位置資料進行++;
4>上述操作完成後,tmp中統計了每個資料在待排序列中出現的次數;
3>最後,將tmp數組裡值不為0的所有下標拷進原序列(注意同一個下標可能有多個重複值,都要進行拷貝,不能遺漏),排序就算完成。
3、計數排序的優化
從上面的例子中可以看出,3是待排序序列的最小值,在tmp陣列中3 之前的位置並沒有什麼卵用,那麼基於節省空間的角度進行考慮,我們可以開闢更小的臨時陣列統計次數,同樣能完成排序。
比如要對1000,999,1008這三數進行排序,按照普通的方法前998個空間都被浪費掉了,所以這時候我們對它進行優化。找出要排序的這組元素中的最大值和最小值,這樣就確定了這組元素的範圍,然後開闢這個範圍加1大小的陣列,然後再將要排序的元素對映到這個新開闢的陣列中就可以了。
結論:計數排序適用於資料比較集中的序列排序。
4、時間複雜度&&空間複雜度
計數排序是一種非比較的排序方法,它的時間複雜度是O(N+K),空間複雜度是0(K),其中K是要排序的陣列的範圍。可以看出計數排序是一種以空間呢換取時間的方法。如果當K>N*logN的時候,計數排序就不是好的選擇了,因為基於比較排序的演算法的下限是O(N*logN)。5、程式碼實現
執行結果:#pragma once #include <iostream> #include <assert.h> using namespace std; void PrintArray(int* a,size_t n) { for (size_t i = 0; i < n; ++i) { cout<<a[i]<<" "; } cout<<endl; } void CountSort(int* a,size_t n) { //查詢待排序序列的最大值 int max = a[0]; for (size_t i = 0; i < n-1; ++i) { if (max < a[i+1]) { max = a[i+1]; } } //開闢臨時陣列用來統計資料出現次數 int* tmp = new int[max+1]; //對臨時陣列初值賦為0 memset(tmp,0,sizeof(int)*(max+1)); //統計資料出現次數 for (size_t i = 0; i < n; ++i) { tmp[a[i]]++; } //將資料考回原陣列 int index = 0; for (int i = 0; i <= max; ++i) { while(tmp[i]--) { a[index] = i; ++index; } } delete []tmp; } //計數排序的優化 void CountSort1(int* a,size_t n) { //找到待排序序列的最大值和最小值 int max = a[0]; int min = a[0]; for (size_t i = 0; i < n-1; ++i) { if (max < a[i+1]) { max = a[i+1]; } if (min > a[i+1]) { min = a[i+1]; } } //開闢適當空間的陣列 int range = max - min + 1; int* tmp = new int[range]; memset(tmp,0,sizeof(int)*(range)); for (size_t i = 0; i < n; ++i) { tmp[a[i]-min]++; } //將資料考回原陣列 int index = 0; for (int i = 0; i < range; ++i) { while(tmp[i]--) { a[index] = i+min; ++index; } } delete []tmp; } void TestCountSort() { int a[] = {2,5,4,9,3,6,8,7,1,0}; int a1[] = {73,20,93,43,55,14,28,65,39,81}; size_t sz = sizeof(a)/sizeof(a[0]); size_t sz1 = sizeof(a1)/sizeof(a1[0]); CountSort(a,sz); CountSort1(a1,sz1); PrintArray(a,sz); PrintArray(a1,sz1); }
二、基數排序
1、基數排序的分類
LSD-- Least Significant Digit first(從低位向高位排)
MSD-- Most Significant Digit first(從高位向低位排)
由於原理差不多,此文只針對LSD進行分析。
2、基本思想
基數排序又稱"桶子法",它從低位開始將待排序的數按照這一位的值放到相應的編號為0到9的桶中。等到低位排完之後得到一個序列,再將這個序列按照次低位的大小進入相應的桶中。以此類推,直到將所有的位都排完之後,這組數就已經有序了。
圖解(假設升序):
3、演算法執行步驟
1》統計待排序序列中最大的數是幾位數;
2》開闢一個與原陣列大小相同的臨時陣列tmp;
3》用一個count陣列統計原陣列中某一位(從個位向高位統計)相同的資料出現的次數;
4》用一個start陣列計算原陣列中某一位(從個位向高位計算)相同的資料出現的位置;
5》將桶中資料從小到大(由頂至底)用tmp陣列收集起來;
6》迴圈3》4》5》直到所有位都被統計並計算過,然後用tmp收集起來;
7》將tmp陣列的資料拷回原陣列,排序完成。
圖解:
4、時間複雜度&&空間複雜度
基數排序是一種非比較排序,它的時間複雜度是O(N*digit),其中digit是這組待排序中的最大的數的數量級。基數排序的空間複雜度就是在分配元素時,使用的桶空間,所以基數排序的空間複雜度是O(N)。它是一種穩定的排序方法。
5、程式碼實現
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
int GetMaxDigit(int* a,size_t n)//得到陣列最大值的位數
{
int digit = 1;
int base = 10;
for (size_t i = 0; i < n; ++i)
{
//每次與幾位數的最小資料(10、100、1000……)進行比較,
//優點是相同位數的資料只統計一次
while(a[i] >= base)
{
++digit;
base *= 10;
}
}
return digit;
}
void LSDSort(int* a,size_t n)
{
assert(a);
int base = 1;
int digit = GetMaxDigit(a,n);//統計位數
int* tmp = new int[n];//開闢臨時陣列
while(digit--)
{
int count[10] = {0};
//統計某位相同的資料出現次數
for (size_t i = 0; i < n; ++i)
{
int index = a[i]/base %10;
count[index]++;
}
int start[10] = {0};
//統計個位相同的數在陣列a中出現的位置
for (size_t i = 1; i < n; ++i)
{
start[i] = count[i-1] + start[i-1];
}
memset(tmp,0,sizeof(int)*n);//為tmp陣列初始化
//從桶中收集資料(從小到大)
for (size_t i = 0; i < n; ++i)
{
int index = a[i]/base %10;
tmp[start[index]++] = a[i];
}
//將資料拷回原陣列
memcpy(a,tmp,sizeof(int)*n);
base *= 10;
}
delete []tmp;
}
void PrintArray(int* a,size_t n)
{
for (size_t i = 0; i < n; ++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void TestLDSort()
{
int a[] = {73,20,93,43,55,14,28,65,39,81};
size_t sz = sizeof(a)/sizeof(a[0]);
LSDSort(a,sz);
PrintArray(a,sz);
}
執行結果: