排序算法——巧妙思路下誕生的計數排序(圖解)

算法過程(圖解)

計數排序是非比較線性時間排序算法,與比較排序算法不同,計數排序不需要比較和交換元素,而是利用輔助空間和元素自身的值來決定元素在已排序序列中的位置。
接下來我們通過圖示瞭解計數排序的過程:
首先,我們假設原序列爲A,裏面包含了六個整數值:
在這裏插入圖片描述
六個整數值中最大值爲 3,最小值爲 -1。所以計算 k = (3 - (-1)) + 1 = 5, k值表示原序列A中從最小值到最大值的閉合區間內一共擁有多少位整數。然後我們創建大小爲 k 的輔助空間C:
在這裏插入圖片描述
這裏的輔助序列C其實有點類似於映射,這也是計數排序的巧妙之處:正常的映射是關鍵詞到值之間的對應關係,然而整數值到整數值之間的映射關係(類似於本文中元素與出現次數之間的關係)則可以通過數組來模擬(比起構建複雜的映射,這樣可以降低算法的運行時間)。C中的下標與元素本身的值有關,而對應下標存儲的值則與該元素在原序列A中出現的頻數有關。我們通過圖示來理解,A中的最小值 min = -1, 然後我們逐個遍歷A中的元素,第一個元素爲 2 ,我們通過 2 - (-1)= 3 ,得到元素 2 在C中的下標應該爲 3,所以 C[3] += 1。然後同樣的,通過 3 - (-1) = 4,我們得到元素 3的下標爲4,C[4] += 1。以此類推,最後我們能得到C序列,其中下標是通過元素自身的值減去最小值得到的,對應下標存儲的是元素在A中出現的頻數。

接下來我們要對C中元素進行修改,使對應下標存儲的值的意義改變。原本對應下標存儲的是元素在A中出現的頻數,現在我們要讓C中所有元素加上前一項的值,例如C[0] 爲 1,現在我們讓 C[1] = 0 + 1 = 1,讓C[2] = 1 + 1 = 2 ,依次進行和操作,得到新的C數組:
在這裏插入圖片描述
這個新的輔助數組中各元素的意義是什麼呢?比如下標 2,對應A中的元素 1 ,它在C中的對應值爲2,這意味着在A序列中,所有小於等於 1 的值一共有 2 個,而 C[4] 的值爲 6,說明在A中小於等於 3 的元素一共有 6 個。得到了新的輔助數組,我們就可以開始排序工作了,首先初始化一個和A大小相等的數組B,然後倒序遍歷序列A中的元素,通過元素自身的值計算下標,在C中找到該下標對應的值,然後通過該值求得該元素在B中的存儲位置,最後將C中對應的值減去1。(原諒我單薄的表述能力,這句話中加粗的都是同一個值)
代碼表述如下:

for(int i = A.size() - 1; i >= 0; --i)	//倒序遍歷
{
	B[C[A[i] - min] - 1] = A[i];
	--C[A[i] - min];
}

第一步圖示如下:
在這裏插入圖片描述
第二步圖示如下:
在這裏插入圖片描述
第三步圖示:
在這裏插入圖片描述
最終結果:
在這裏插入圖片描述

程序演示(C++)

void count_sort(vector<int>& v)
{
	/*得到最大值和最小值*/
	int min = v[0], max = v[0];
	for (size_t i = 1; i < v.size(); ++i)
	{
		if (v[i] > max)
			max = v[i];
		else if (v[i] < min)
			min = v[i];
	}

	/*創建大小爲k = max - min + 1 的輔助數組*/
	vector<int> times(max - min + 1);	
	for (size_t m = 0; m < v.size(); ++m)
		++times[v[m] - min];

	for (size_t k = 1; k < times.size(); ++k)
		times[k] += times[k - 1];

	/*根據原序列和輔助數組,得到排序序列B,並且覆蓋原序列中的值*/
	vector<int> B(v.size());
	for (int j = v.size() - 1; j >= 0; --j)
	{
		B[times[v[j] - min] - 1] = v[j];
		--times[v[j] - min];
	}

	for (size_t i = 0; i < B.size(); ++i)
		v[i] = B[i];
}

思考

我們最後來回過頭思考一下爲什麼要限定原序列中存儲的是整數?爲什麼最後構建有序序列時要倒序遍歷原序列?以及這個計數排序算法的算法複雜度是多少?

一些細節

首先是爲什麼要限定原序列中存儲的元素是整數?要知道我們一開始定義輔助結構大小時,是通過計算k = max - min + 1得到的,如果原序列中存儲有浮點數的話,那麼我們就不能根據閉區間[min, max]中整數個數算得輔助結構的大小了。所以限制是整數純粹只是爲了限制C數組的大小。

至於倒序的原因:我們可以觀察一下原序列中的元素3,我們可以看到元素3在原序列中下標爲1和5,這兩個元素在已排序序列中下標對應爲4和5,也就是說所有元素仍保持了之前的相對位置,保證了算法是穩定的。

算法複雜度

我們在算法過程中首先遍歷了原序列數組,時間複雜度爲O(n);隨後遍歷了輔助數組,時間複雜度爲O(k);最後將數據插入排序數組,時間複雜度爲O(n),所以總的時間複雜度爲O(n + k),其中k爲原序列中整數範圍 [max, min] 內整數的數目,如果k = O(n2)或更大,那麼算法的效率就不如比較排序效率高了,所以我們能得到該算法的適用場景僅限於k = O(n)的情況。
該算法創建了輔助數組(大小爲k),還創建了數組B容納原數組中的元素(大小爲n)。所以該算法的空間複雜度爲O(n + k),一定程度上屬於犧牲了空間換取時間的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章