1、計數排序
計數排序 需要滿足 n 個輸入元素中的每一個元素都在 0 到 k 的區間內的整數。
計數排序的思想是:對每一個輸入元素x,確定小於 x 的元素個數。利用這一信息,可以直接把 x 放到它在輸出數組中的位置上。例如,有17個元素小於 x,則 x 就應該在第18個位置上。
計數排序 需要使用 k+n個輔助空間,k 個輔助空間是用來確定小於 x 的元素個數,n 個輔助空間是用來存放對原數組的排序輸出。我們把 k 個輔助空間數組假設爲 C, n個輔助空間數組假設爲 B,需要排序的元素組假設爲 A,具體代碼如下:
//arrs 是需要排序的數組,inum是數組的個數,ik 是 輸入元素的範圍
Counting_Sort(int A[], int inum,int ik)
{
//1、首先對C數組進行初始化
int C[ik];
for(int i = 0; i<ik; i ++)
C[i] = 0;
//2、遍歷A數組,會記錄每個元素出現的次數,並放到C數組中
for(int i = 0; i< inum; i++)
C[A[i]] ++;
//3、計算小於等於某一個值的元素個數
for( int i = 1; i<ik; i++)
C[i] = C[i] + C[i-1];
//4、確定A數組中元素應該在的位置,主要需要從後往前遍歷
for( int i = inum; i > 0; i--)
{
int index = C[A[i]]; //小於等於A[i] 元素的個數
B[index] = A[i]; //把A[i] 元素放入應該在的地方
C[A[i]] = --index; //因爲減少了元素,對應放置的位置往前一位
}
}
如果光看代碼可能有點繞,這是一個動態的圖,方便理解:
2、桶排序
桶排序 的原理是將數組分到有限數量的桶子裏,每個桶在進行單獨排序(有可能在使用別的排序算法),最後把排序之後的數據串聯起來。當要被排序的數組內的數值是均勻分配的話,桶排序的時間複雜度爲 O(n),當然這是最理想的情況。要進行桶排序的數據必須介於 0~k 之間 或者 (0,1)的浮點數也可。
桶排序的過程可以描述成以下幾個步驟:
1、根據數據大小分配合適的桶子個數 M,每個桶子的數據限定在一定範圍內。
2、遍歷將要排序的數據 N,將每個元素按照規定的範圍分佈到各個桶中。
3、對每個桶子中的元素進行排序,排序算法可選擇其他算法
4、依次從每個桶中取出元素,按順序放入到最初的輸出序列中。
桶排序的時間複雜度,還跟每個桶選擇的算法有關,假設有 M 個桶,每個桶的元素爲 n/m
當桶中使用冒泡排序時,總的時間複雜度爲 O(n)+mO((n/m)2).
當桶中使用快速排序時,總的複雜度爲 O(n) + mO( n/m log(n/m));
當桶的個數越多,執行的效率越快,但是桶越多,空間消耗就越大,是一種通過空間換時間的方式。
桶排序的僞代碼如下:
Bucket_sort( A )
{
int n = A.length
//1、創建桶,並初始化
let B[0... n-1] be a new array
for i = 0 to n-1
make B[i] an empty list
//2、遍歷排序數組,並把對應元素放到對應桶中
for i = 1 to n
insert A[i] into list B[LnA[i]]
//3、對每個桶進行排序
for i = 0 to n -1
sort list B[i] with sort
//4、把數據從每個桶中提取出來
concatenate the lists B[0],B[1],B[2]...... together in order
}
3、基數排序
基數排序 其原理:是將整數按位數切割成不同的數字,然後按每個位數分別排序。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是隻能使用於整數。
從直觀上看,會覺得應該按照 最高有效位進行排序(MSD),但是對於數字的排序是從最低有效位進行排序(LSD),即先對最次位關鍵字進行排序,再對高一位的關鍵字進行排序,以此類推。
代碼如下:
//此函數的目的是取得數據每個位上的數值
//i爲待取的數據
int getDigit(int i, int d) //d的值爲1、2、3...,表示要求取的相應位的值,1表示求取個位,
{ //2表示十分位,類推
int val;
while (d--)
{
val = i % 10;
i /= 10;
}
return val;
}
//基數排序算法的具體實現
void Radix_Sort(int *list, int begin, int end, int digit)
{
int radix = 10; //基數
int i = 0, j = 0;
int * count = new int[radix]; //存放當前元素的個數數組,使用了計數排序的技巧
int * pB = new int[end - begin + 1];
//1、按照最多的位數,來進行遍歷排序,如果位數不夠,高位補0
for (int d = 1; d <= digit; d++)
{
for ( i = 0; i < radix; i++)
count[i] = 0; //置空輔助數組
for (i = begin; i <= end; i++)
{
j = getDigit(list[i], d);
count[j]++;
}
for (i = 1; i < radix; i++)
count[i] = count[i] + count[i - 1]; //計算小於等於當前元素的個數
//將數據依次裝入桶中,保證數據的穩定性,此步即爲基數排序的分配
for (i = end; i >= begin; i--)
{
j = getDigit(list[i], d);
pB[count[j] - 1] = list[i];
count[j]--;
}
//基數排序的收集
//把輔助數組的數據再倒出來
for (i = begin, j = 0; i <= end; i++, j++)
list[i] = pB[j];
}
}
便於理解,下面是是動態圖片:
基數排序是否比快速排序更好呢?通常基數排序的時間複雜度爲O(n),而快速排序則需要O(nlgn),從結果上看,基數排序更好一些。但是基數排序雖然循環的輪數比快速排序少,但每一輪所耗費的時間要長的多。哪一個排序算法更好一些,則需要看具體的情況了。
4、總結
今天總結的這三種排序時三種線性時間複雜度的排序算法,都沒有使用 比較操作。但是同樣的三者都需要使用輔助內存來進行排序,典型的以空間換時間的操作。
從網上看的資料,感覺現在的文章複製的較多,桶排序和計數排序都給搞混了,計數排序的舉例也是使用了每個輔助空間只存儲單一元素的這種特例,看起來和桶排序是比較類似。所以如果大家有時間,還是找本書來進行系統的學習,別讓網上的片段資料給帶偏了。
感謝大家,我是假裝很努力的YoungYangD(小羊)。
參考資料:
《算法導論》
https://www.runoob.com/w3cnote/radix-sort.html
https://www.cnblogs.com/dwj411024/p/5978821.html
https://blog.csdn.net/qq_39942341/article/details/82379334