1. 程式人生 > >線性排序演算法(計數排序和桶排序) C++

線性排序演算法(計數排序和桶排序) C++

前言:

比較排序的下界為o(nlogn)。那麼有沒有時間複雜度為o(n)的線性時間排序演算法呢?在一定的假設條件下,是有更快的排序演算法的,下面介紹的計數排序和桶排序等都是線性時間排序演算法。

1、計數排序

計數排序便是很基礎的一種線性時間排序,它是基數排序的基礎。基本思想是:對每一個元素x,確定小於x的元素個數,就可以把x直接放到它在有序序列中的位置上。過程描述:假設待排序序列a中值的範圍[0,k],其中k表示待排序序列中的最大值。首先用一個輔助陣列count記錄各個值在a中出現的次數,比如count[i]表示i在a中的個數。然後依次改變count中元素值,使count[i]表示a中不大於i的元素個數。然後從後往前掃描a陣列,a中的元素根據count中的資訊直接放到輔助陣列b中。最後把有序序列b複製到a。

vector<int> sortCouting(const vector<int>& v, int mm) {
	vector<int> coutv(mm + 1, 0),ret(v.size());
	for (auto vi : v) ++coutv[vi];
	for (int k1(1); k1 <= mm; ++k1) coutv[k1] += coutv[k1 - 1];
	for (int k1(v.size() - 1); k1 >= 0; --k1) ret[--coutv[v[k1]]] = v[k1];
	return ret;
}


2、基數排序

在計數排序中,當k很大時,時間和空間的開銷都會增大(可以想一下對序列{8888,1234,9999}用計數排序,此時不但浪費很多空間,而且時間方面還不如比較排序)。於是可以把待排序記錄分解成個位(第一位)、十位(第二位)....然後分別以第一位、第二位...對整個序列進行計數排序。這樣的話分解出來的每一位不超過9,即用計數排序序列中最大值是9。

3、桶排序

基本原理:同計數排序一樣,桶排序也對待排序序列作了假設,桶排序假設序列由一個隨機過程產生,該過程將元素均勻而獨立地分佈在區間[0,1)上。基本思想是:把區間[0,1)劃分成n個相同大小的子區間,稱為桶。將n個記錄分佈到各個桶中去。如果有多於一個記錄分到同一個桶中,需要進行桶內排序。最後依次把各個桶中的記錄列出來記得到有序序列。拓展:桶排序是在已經資料的範圍的條件下,建立若干個桶,根據相應的比較規則將待排資料落入各個對應的桶中,最後掃描 桶 來實現排序。如果要排序的物件不是小數型,而是整合的集合,就有下面的結論和應用。例如要對大小為[1..1000]範圍內的n個整數A[1..n]排序,可以把桶設為大小為10的範圍,具體而言,設集合B[1]儲存[1..10]的整數,集合B[2]儲存(10..20]的整數,……集合B[i]儲存((i-1)*10, i*10]的整數,i = 1,2,..100。總共有100個桶。然後對A[1..n]從頭到尾掃描一遍,把每個A[i]放入對應的桶B[j]中。 然後再對這100個桶中每個桶裡的數字排序,這時可用冒泡,選擇,乃至快排,一般來說任何排序法都可以。最後依次輸出每個桶裡面的數字,且每個桶中的數字從小到大輸出,這樣就得到所有數字排好序的一個序列了。再例如,假設有10萬個人的年齡資料,年齡範圍預設是0-99,如何對這10萬個資料進行排序?如果用快排啊、歸併排序啊...這樣的排序演算法是可以。但是這樣的排序問題更適合桶排序。採用桶排序的方法如下:建立100個桶,這可以用一個 一維陣列來表示。a[0...99],依次掃描10萬條資料,根據每條資料的值,記錄到桶中。比如,第10個人的年齡是18歲,則a[18]++ (這是將出現的頻率記錄在桶中,是計數,它是將待排序的元素本身進行比較,而不是將“待排序的元素的組成部分”進行比較)然後,掃描這100個桶,即可得到有序的陣列。

vector<int> sortBucket(vector<int>& v, int mm) {
	vector<int> bucket(mm + 1, 0);
	for (auto vi : v) ++bucket[vi];
	int cn(0);
	for (int k1(0); k1 <= mm; ++k1) {
		if (bucket[k1]) {
			while(bucket[k1]--) v[cn++] = k1;
		}
	}
	return v;
}