什麼是計數排序?

2021-10-07 01:15:57 字數 3611 閱讀 2819

有這樣一道排序題:陣列裡有20個隨機數,取值範圍為從0到10,要求用最快的速度把這20個整數從小到大進行排序。

第一時間你可能會想使用快速排序,因為快排的時間複雜度只有o(nlogn)。但是這種方法還是不夠快,有沒有比o(nlogn)更快的排序方法呢?你可能會有疑問:o(nlogn)已經是最快的排序演算法了,怎麼可能還有更快的排序方法?

讓我們先來回顧一下經典的排序演算法,無論是歸併排序,氣泡排序還是快速排序等等,都是基於元素之間的比較來進行排序的。但是有一種特殊的排序演算法叫計數排序,這種排序演算法不是基於元素比較,而是利用陣列下標來確定元素的正確位置。

在剛才的題目裡,隨即整數的取值範圍是從0到10,那麼這些整數的值肯定是在0到10這11個數裡面。於是我們可以建立乙個長度為11的陣列,陣列下標從0到10,元素初始值全為0,如下所示:

有了這個統計結果,排序就很簡單了,直接遍歷陣列,輸出陣列元素的下標值,元素的值是幾,就輸出幾次:

0, 1, 1, 2, 3, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 9, 9, 9, 9, 10

顯然,這個輸出的數列已經是有序的了。

這就是計數排序的基本過程,它適用於一定範圍的整數排序。在取值範圍不是很大的情況下,它的效能在某些情況甚至快過那些o(nlogn)的排序,例如快速排序、歸併排序。

**實現如下:

public

static

int[

]countsort

(int

array)

//2.根據數列的最大值確定統計陣列的長度

int[

] coutarray =

newint

[max +1]

;//3.遍歷數列,填充統計陣列

for(

int i =

0; i < array.length; i++

) coutarray[array[i]]++

;//4.遍歷統計陣列,輸出結果

int index =0;

int[

] sortedarray =

newint

[array.length]

;for

(int i =

0; i < coutarray.length; i++)}

return sortedarray;

}

這段**在一開始補充了乙個步驟,就是求得數列的最大整數值max,後面建立的陣列countarray,長度就是max+1,以此保證陣列最後乙個下標是max。

從功能角度來看,這段**可以實現整數的排序。但是這段**其實並不嚴謹。

比如這個數列:95, 94, 91, 98, 99, 90, 99, 93, 91, 92。該數列最大值是99,但最小值是90,如果我們只以數列的最大值來決定統計陣列的長度的話,就要建立長度為100的陣列,那麼就會浪費前面90個空間。

為了解決這個問題,我們不再以(輸入數列的最大值+1)作為統計陣列的長度,而是以(數列最大值和最小值的差+1)作為統計陣列的長度。同時,數列的最小值作為乙個偏移量,用於統計陣列的對號入座。

以剛才的數列為例,統計陣列的長度為 99-90+1=10,偏移量等於數列最小值90。

對於第乙個整數95,對應的統計陣列下標為95-90=5,如圖所示:

這是一方面,另外,上述**知識簡單地按照統計陣列的下標輸出了元素值,並沒有真正給數列排序。如果僅僅只是給整數排序,這樣並沒有問題。但如果是在現實業務裡,比如給學生的考試分數排序,如果遇到相同的分數就會分不清誰是誰。看看下面這個例子:

給出乙個學生的成績表,要求按成績從底到高排序,如果成績相同,則遵循原表固有順序。

當我們填充統計陣列之後,我們只知道有兩個成績並列95分的學生,卻不知道誰是小紅,誰是小綠:

對此,我們只需在填充完統計陣列之後,對統計陣列做一下變形。我們仍然以學生的成績表為例,把之前的統計陣列進行變形,統計陣列從第二個元素開始,每乙個元素都加上前面所有元素之和:

相加的目的就是為了讓統計陣列儲存的元素值等於相應整數的最終排序位置。比如下標是9的元素值是5,代表原始數列的整數9最終的排序是在第5位。

接下來,我們建立輸出陣列sortedarray,長度和輸入數列一致,然後從後向前遍歷輸入數列:

第一步,遍歷成績表最後一行的小綠:小綠是95分,找到countarray下標為5的元素,值是4,代表小綠的成績排名是在第4位。

同時給countarray下標是5的元素值減1,從4變成3,代表著下次再遇到95分時,最終排名是第3位。

第二步,遍歷成績表倒數第二行的小白:小白是94分,找到countarray下標是4的元素,值是2,代表小白的成績排名在第2位。

同時,給countarray下標是4的元素值減1,從2變成1,代表下次再遇到94分的成績時(實際上已經遇不到了),最終排名是第1位。

第三步,遍歷成績表倒數第三行的小紅:小紅是95分,找到countarray下標是5的元素,值是3(最初是4,減1變成了3),代表小白的成績排名在第3位。

同時,給countarray下標是5的元素值減1,從3變成2,代表下次再遇到95分的成績時(實際上已經遇不到了),最終排名是第2位。

因此,同樣是95分的小紅和小綠就能清楚地排出順序,所以優化版的計數排序屬於穩定排序。

後面的遍歷過程依此類推。

改進版本的計數排序**如下:

public static int countsort(int array)

if(array[i] < min)

}int d = max - min;

//2.建立統計陣列並計算統計對應元素個數

int countarray = new int[d + 1];

for (int i = 0; i < array.length; i++)

//3.統計陣列變形,後面的元素等於前面的元素之和

int sum = 0;

for (int i = 0; i < countarray.length; i++)

//4.倒序遍歷原始陣列,從統計陣列找到正確位置,輸出到結果陣列

int sortedarray = new int[array.length];

for (int i = array.length - 1; i > 0; i–)

return sortedarray;

}如果原始數列的規模是n,最大最小整數的差值是m,由於**中第1、2、4步都涉及到遍歷原始數列,運算量都是n,第3步遍歷統計數列,運算量是m,所以總體運算量是3n+m,去掉係數,時間複雜度是o(n+m)。

至於空間複雜度,如果不考慮結果陣列,只考慮統計陣列的話,空間複雜度是o(m)。

雖然計數排序看上去很強大,但是它存在兩大侷限性:

1.當數列最大最小值差距過大時,並不適用於計數排序

比如給定20個隨機整數,範圍在0到1億之間,此時如果使用計數排序的話,就需要建立長度為1億的陣列,不但嚴重浪費了空間,而且時間複雜度也隨之公升高。

2.當數列元素不是整數時,並不適用於計數排序

如果數列中的元素都是小數,比如3.1415,或是0.00000001這樣子,則無法建立對應的統計陣列,這樣顯然無法進行計數排序。

正是由於這兩大侷限性,才使得計數排序不像快速排序、歸併排序那樣被人們廣泛適用。

什麼是計數排序?

public static int countsort int array 2.根據數列最大值確定統計陣列的長度 int countarray newint max 1 3.遍歷數列,填充統計陣列 for inti 0 i countarray array i 4.遍歷統計陣列,輸出結果 intin...

什麼是計數排序?

計數排序其實是桶排序的一種特殊情況。當要排序的 n 個資料,所處的範圍並不大的時候,比如最大值是 k,我們就可以把資料劃分成 k 個桶。每個桶內的資料值都是相同的,省掉了桶內排序的時間。我們都經歷過高考,高考查分數系統你還記得嗎?我們查分數的時候,系統會顯示我們的成績以及所在省的排名。如果你所在的省...

什麼是計數排序?

假設,有20個隨機整數,取值範圍是0到10,需要對其排序。可能第一反應是使用快速排序啊,快排的時間複雜度是o nlog n 但是,可不可以比o nlog n 更快呢?這就是這篇文章要介紹的計數排序 從名字上來看,就是計算數字出現頻次的排序方法,非常的見名知意 因為取值範圍是0到10 整數的值在0,1...