非比較排序之 計數排序與基數排序
非比較排序
與插入、希爾、快速、歸併、堆排序等等排序方式不同的是
以上這些排序演算法都涉及到待排序列中元素值的比較。
然而也有不需要比較的排序演算法。
計數排序
計數排序主要思想:
給定一組要排序的序列,找出這組序列中的最大值,然後開闢一個最大值加1大小的陣列,將這個數組裡面的元素全部置零,然後用這個陣列下標統計出要排序的序列中各個元素出現的次數。等到統計完成的時候,排序就已經完成。
計數排序是一種非比較的排序方法,它的時間複雜度是O(N+K),空間複雜度是0(K),其中K是要排序的陣列的範圍。可以看出計數排序是一種以空間呢換取時間的方法。如果當K>N*logN的時候,計數排序就不是好的選擇了,因為基於比較排序的演算法的下限是O(N*logN)。
優化:
可以看出,計數排序使用於待排序的元素值比較集中的情況。假如先在對1001,1005,1008這三數進行排序,按照上面的方法前1000個空間都被浪費掉了,所以這時候我們對它進行優化。
我們可以找出要排序的這組元素中的最大值和最小值,這樣就確定了這組元素的範圍,然後開闢這個範圍加1大小的陣列,然後再將要排序的元素對映到這個新開闢的陣列中就可以了。
計數排序適用於數字最大與最小差別不大的序列。
template<class T>
void CountSort(T* arr, size_t n)
{
assert(arr);
T max = arr[0];
T min = arr[0 ];
for (int i = 0; i < n; ++i){
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
size_t range = max - min + 1;
//T* tmp = new T[range];
//memset(tmp, 0, sizeof(int)*range);
vector<T> tmp;
tmp.resize(range, 0);
for (int i = 0; i < n; ++i){
tmp[arr[i] - min]++;
}
size_t index = 0 ;
for (int i = 0; i < range; ++i){
while (tmp[i]--){
arr[index++] = i + min;
}
}
//delete[] tmp;
}
基數排序
基數排序的主要思想:
基數排序又稱”桶子法”,他從低位開始將待排序的數按照這一位的值放到相應的編號為0到9的桶中。等到低位排完之後得到一個序列,再將這個序列按照次低位的大小進入相應的桶中。以此類推,直到將所有的位都排完之後,這組數就已經有序了。
基數排序是一種非比較排序,它的時間複雜度是O(N*digit),其中digit是這組待排序中的最大的數的數量級,它的空間複雜度是O(N)。它是一種穩定的排序方法。
實現:
對於桶,我們最直觀的想法就是用容器作為桶,將每一個桶的元素都放到一個容器中,這樣雖然也能做,但是還是實現起來不是那麼容易。我們可以參考稀疏矩陣的轉置。例如對個位數的排序,我們開闢一個10元素大小的陣列,將這10個大小的陣列假想成10個桶,統計出每個桶中第一個元素在原陣列中的起始位置。我們在建立一個與要排序的序列一樣大輔助陣列,然後根據每個桶中的起始位置將每個桶中的元素依次放到輔助陣列中,之後再把輔助陣列中的元素拷貝回原陣列,這樣一次排序就完成了。之後再按照同樣的方法,再對高位進行排序。
程式碼:
template<class T>
int Count(T* arr, size_t n)
{
int digit = 1;
int base = 10;
for (int i = 0; i < n; ++i){
while (base <= arr[i]){
base *= 10;
digit++;
}
}
return digit;
}
template<class T>
void BaseSort(T* arr, size_t n)
{
assert(arr);
int digit = Count(arr, n);
int base = 1;
vector<T> tmp;
tmp.resize(n, 0);
while (digit--){
int count[10] = { 0 };
for (int i = 0; i < n; ++i){
int index = arr[i] / base % 10;
count[index]++;//求這一位數字是出從零到9的那個數字出現的次數。
}
int start[10] = { 0 };
for (int i = 1; i < 10; ++i){
start[i] = start[i - 1] + count[i - 1];
}//求這一位數字是從零到9那個數字在輔助空間裡第一次出現的位置。
for (int i = 0; i < n; ++i){
int index = arr[i] / base % 10;
tmp[start[index]++] = arr[i];//請注意這裡 這時的arr[i]是上一次迴圈從輔助陣列拷貝過來的 。
//arr[i]中的數列是按照比這一次迴圈處理的位第一位數字從小到大排列的
//這裡往tmp裡填的是這一位相同數字第一位數字中較小的元素。
}
for (int i = 0; i < n; ++i){
arr[i] = tmp[i];//把輔助空間裡的拷貝回原空間。
}
base *= 10;//下一個迴圈處理高一位的數字。
}
}