聊一聊那些線性時間複雜度的排序演算法

2022-07-26 11:15:09 字數 4067 閱讀 3196

實際上,基於比較和交換的排序演算法,它們的時間複雜度的下限就是o(nlog2

n)。氣泡排序,插入排序等自不必多說,時間複雜度是o(n2),即使強如快速排序,堆排序等也只是達到了o(nlog2

n)的複雜度。那麼那些傳說中可以突破o(nlog2

n)下限,達到線性時間複雜度o(n)的排序演算法到底是什麼樣的呢,接下來讓我們一**竟。

一句話概括就是,將待排序列中的每乙個元素通過設定好的對映函式分配到有限數量的桶中,然後再對每個桶中的元素排序。

基本步驟如下:

準備有限數量的空桶

遍歷待排序列,將每個元素通過對映函式分配到對應的桶中

對每個不是空的桶進行排序

從每個不是空的桶中再依次把元素放回到原來的序列中

桶排序是利用函式的對映完成了元素的劃分,省略了比較交換的步驟,然後再對桶中的少量資料進行排序,這裡的排序可以根據實際需求選擇任意的排序演算法,比如使用快速排序。需要注意的是對映函式的選擇必須保證每個桶是有序的,即乙個桶中的所有元素必須大於或小於另乙個桶中的所有元素。這樣才能在依次從每個桶中將元素放回到原始序列中時,保證元素的有序性。

public void bucketsort(int array)

list buckets = new list[fun(max, min, array.length) + 1];

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

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

int index = 0;

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

}}// 對映函式,可以根據實際需求選擇不同的對映函式

public int fun(int value, int minvalue, int length)

【演算法解讀】演算法首先確定對映函式fun,函式的返回值就是元素對應桶的下標。然後找到待排序列中的最大值與最小值,並利用最大最小值確定桶排序需要的桶數量。遍歷待排序列的所有元素並通過對映函式fun將它們分配到對應下標的桶中。再依次對每個桶內的所有元素進行排序,這裡的使用的是c#提供的sort方法(也可以選擇不同的排序方法)。當乙個桶內的元素排序完畢後再將其放回到原始序列中。

【舉個栗子】

對於待排序列4, 7, 1, 16, 6可以使用下圖表示其桶排序的過程:

計數排序可以認為是桶排序的一種特殊實現,如果理解了桶排序的話,計數排序就相對很簡單了。

計數排序要求待排序列的所有元素都是範圍都在[0, max]之間的正整數(當然經過變形也可以是負數,比如通過加上某個值,使所有元素都變為正數)

基本步驟如下:

得到待排序列中的最大值,構建(最大值 + 1)的計數陣列c,可以認為是(最大值 + 1)個桶,只是桶中存放的不再是元素,而是每個元素出現的次數

遍歷待排序列,在計數陣列中統計每個元素出現的次數。出現乙個元素i,則以該元素值為索引的位置上計數加1,即c[i] ++

遍歷計數陣列,若c[i] > 0,則表示存在有值為i的元素,依次將存在的元素i賦值到原始序列中

時間複雜度:o(n + k),常數k表示待排序列中最大元素的值

最好情況:o(n + k)

最壞情況:o(n + k)

穩定性:穩定

優點:穩定,適用於最大值不是很大的整數序列,在k值較小時突破了基於比較的排序的演算法下限

缺點:存在前提條件,k值較大時,需要大量額外空間

public void countsort(int array)

int count = new int[max + 1];

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

int index = 0;

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

【演算法解讀】可以看到演算法首先獲得待排序列元素中的最大值,然後構建(最大值+1)長度的計數陣列。遍歷待排序列的每個元素,並在計數陣列中利用元素值為下標記錄元素的出現次數。然後遍歷計數陣列,若對應下標的位置上值大於0(等於幾就表示有幾個元素),則表示存在有元素且其值為下標的大小。將該元素新增到原始序列中。由於下標是從小到大的,所以對應得到的序列也是從小到大排列的。

【舉個栗子】

對於待排序列1, 2, 3, 4, 5可以使用下圖表示其計數排序的過程:

基數排序也是不進行比較,而是通過「分配」和「收集」兩個過程來實現排序的。

首先設立r個佇列,對列編號分別為0~r-1,r為待排序列中元素的基數(例如10進製數,則r=10),然後按照下面的規則對元素進行分配收集:

先按最低有效位的值,把n個元素分配到上述的r個佇列中,然後從小到大將個佇列中的元素依次收集起來

再按次低有效位的值把剛收集起來的元素分配到r個佇列中,然後再進行收集

重複地進行上述分配和收集,直到最高有效位。(也就是說,如果位數為d,則需要重複進行d次,d由所有元素中最大的乙個元素的位數計量,比如如果最大數是963,則d = 3)

為什麼這樣就可以完成排序呢?

以從小到大排序為例,首先當按照最低有效位完成分配和收集後,此時得到的序列,是根據元素最低有效位的值從小到大排列的。

當按照次低有效位進行第二次分配和收集後,得到的序列,是先根據元素的次低有效位的值從小到大排列,然後再根據最低有效位的值從小到大排列。

以此類推,當按照最高有效位進行最後一次分配和收集後,得到的序列,是先根據元素的最高有效位的值從小到大排列,再根據次高有效位排列,。。。,再根據次低有效位,再根據最低有效位。自然就完成了每個元素的從小到大排列。

public void radixsort(int array)

// 使每個桶記錄的數表示在buffer陣列中的位置

for(int j = 1; j < buckets.length; j ++)

// 收集,將桶中的資料收集到buffer陣列中

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

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

// 清空桶

for(int j = 0; j < buckets.length; j ++)

}}// 獲得待排序列中的最大元素

public int getmaxvalue(int array)

}return max;

}

【演算法解讀】演算法針對的是十進位制數,所以r=10

首先獲取到待排序列中的最大元素,然後根據最大元素的位數d進行d趟分配與收集

每一趟的分配與收集過程如下:

根據給定的位數,計算待排序列的每個元素在該位上的值,如果某個元素沒有該位,則用0表示。然後使用長度為10的桶陣列記錄它們出現的次數(這裡並沒有直接使用桶記錄對應的元素)。再遍歷一遍桶陣列buckets[j] += buckets[j - 1];,使每個桶記錄的數值,表示的就是在指定位數上值等於桶索引的元素在輔助陣列buffer中的位置。

然後進行收集工作,根據桶陣列中記錄的位置資訊,依次將對應的元素收集到buffer陣列中。最後清空桶,為下一次分配收集做準備。

【舉個栗子】

對於待排序列9, 40, 123, 56, 7,17可以使用下圖表示其基數排序的過程:

上面演算法的原始碼都放在了github上,感興趣的同學可以點選這裡檢視

更多演算法的總結與**實現(不僅僅是排序演算法),可以檢視github倉庫algorithm

聊一聊系列 聊一聊移動web解析度的那些事兒

不同於pc時代,移動web的樣式更加多樣,也由於手機解析度的碎片化,移動web的相容問題日益突出,下面,我就和各位讀者一起聊聊移動web所面臨的手機解析度問題。在pc時代,我們書寫css的時候,理所應當的認為,我們所書寫的1px,在螢幕上就是1px的寬度。但是到了移動端,事情就不是這樣了,我們所書寫...

排序演算法 線性時間複雜度

一說到排序演算法,大部分人都會說出著名的萬金油 快速排序 大資料分而治之 歸併排序 大資料排名 堆排序。這些排序無論在面試還是實際專案中,都是經常用到的一些排序演算法,其平均時間複雜度都在 o n log2n 那今天我們就來介紹幾種 o n 的排序演算法。1,計數排序,輸入 n 個範圍在 0 k 區...

聊一聊Python中,if與elif的那些事兒

作為新手,還真是時常會忘記適用 elif 這個好用的判斷方法。或者乾脆不知道什麼時候適用 elif。只用 if 進行判斷和 if 與 elif 一起搭配判斷,有什麼區別?elif的適用情況有哪些?話不多說來看例子 在學習群裡看到有個丟擲這樣一段 先來猜猜最終會列印什麼?x 10 y 1 if x 2...