c++ 實現十三個排序方法 附測試結果加上簡單講解(更新中)

數據測試的種子爲 1003166545645613

long long seed = 1003166545645613;
srand(seed);

測試數據都是隨機生成的,規模爲10^7個數字

stl的sort用了13s

排序中的一個小優化,把swap函數展開寫能加快速度,比如快速排序中單向遍歷時,不展開swap函數用了12s,展開之後用了5s

冒泡排序(>>1min)

方法和名字一樣,每次比較相鄰的兩個元素,在每次遍歷的過程中會將最小的元素浮到數組頂端(也可以是最大的元素,取決於升序還是降序)

未優化版本

const int MAXN = 10000000;
int n;
int a[MAXN];
void bubbleSort() {
	for (int i = 0; i < n; ++i) {
		for (int j = n - 1; j > i; --j) {
			if (a[j - 1] > a[j]) {
				swap(a[j - 1], a[j]);
			}
		}
	}
}

優化版本

const int MAXN = 10000000;
int n;
int a[MAXN];
void bubbleSort() {
	for (int i = 0; i < n; ++i) {
		bool swapped = false;
		for (int j = n - 1; j > i; --j) {
			if (a[j - 1] > a[j]) {
				swap(a[j - 1], a[j]);
				swapped = true;
			}
		}
		if (!swapped) {
			return;
		}
	}
}

插入排序(>1min)

方法同名字,每次將新增元素(也就是下標爲i的元素)插入到已經排序好的部分中(下標在[0, i)中的元素),可以用二分查找優化插入步驟,每次插入後需要將元素進行移位覆蓋(這裏會花掉很多時間)

const int MAXN = 10000000;
int n;
int a[MAXN];
inline void insertionSort() {
	for (int i = 1; i < n; i++) {
		int temp = a[i];
		int j = i - 1;
		while (j >= 0 && a[j] > temp) {
			a[j + 1] = a[j];
			j--;
		}
		a[j + 1] = temp;
	}
}

雞尾酒排序(>1min)

優化版的冒泡排序,相當於雙向冒泡,比冒泡快很多但還是不行

const int MAXN = 10000000;
int n;
int a[MAXN];
void cocktailSort() {
	int lo = 0;
	int hi = n - 1;
	bool swapped = true;
	while (swapped) {
		swapped = false;
		for (int i = lo; i < hi; ++i) {
			if (a[i] > a[i + 1]) {
				swap(a[i], a[i + 1]);
				swapped = true;
			}
		}
		--hi;
		for (int i = hi; i > lo; --i) {
			if (a[i] < a[i - 1]) {
				swap(a[i], a[i - 1]);
				swapped = true;
			}
		}
		++lo;
	}
}

選擇排序(>1min)

每次找到一個當前未排序好的序列中的最大值或者最小值放在隊首或隊尾

const int MAXN = 10000000;
int n;
int a[MAXN];
void selectionSort() {
	for (int i = 0; i < n; ++i) {
		int pos = i;
		for (int j = i + 1; j < n; ++j) {
			if (a[j] < a[pos]) {
				pos = j;
			}
		}
		swap(a[i], a[pos]);
	}
}

快速排序(單向5.2s,雙向1.2s)

兩種實現方法,一種利用partition函數,然後單向遍歷,或者選擇此處使用的雙向遍歷

單向遍歷

const int MAXN = 10000000;
int n;
int a[MAXN];
inline int partition(int left, int right) {
	swap(a[left], a[(rand() % (right - left + 1)) + left]);
	int pivot = a[left];
	int pivotpos = left;
	for (int i = left + 1; i <= right; ++i) {
		if (a[i] < pivot) {
			++pivotpos;
			if (i != pivotpos) {
				int tmp = a[i];
				a[i] = a[pivotpos];
				a[pivotpos] = tmp;
			}
		}
	}
	a[left] = a[pivotpos];
	a[pivotpos] = pivot;
	return pivotpos;
}
void quickSort(int left, int right) {
	if (left < right) {
		int mid = partition(left, right);
		quickSort(left, mid - 1);
		quickSort(mid + 1, right);
	}
}

雙向遍歷(這裏的right是最後一個元素的下標)

const int MAXN = 10000000;
int n;
int a[MAXN];
void quickSort(int left, int right) {
	int i = left;
	int j = right;
	int mid = a[(left + right) >> 1];
	while (i <= j) {
		while (a[i] < mid) {
			++i;
		}
		while (mid < a[j]) {
			--j;
		}
		if (i <= j) {
			int tmp = a[i];
			a[i++] = a[j];
			a[j--] = tmp;
		}
	}
	if (left < j) {
		quickSort(left, j);
	}
	if (i < right) {
		quickSort(i, right);
	}
}

歸併排序(1.2s)

有原地排序和非原地排序兩個版本,非原地排序時間複雜度太高,不推薦使用

const int MAXN = 10000000;
int n;
int a[MAXN];
int ra[MAXN];
inline void merge(int left, int mid, int right) {
	int i = left;
	int j = mid + 1;
	int k = left;
	while (i <= mid || j <= right) {
		if ((j > right) || (i <= mid && a[i] <= a[j])) {
			ra[++k] = a[i++];
		}
		else {
			ra[++k] = a[j++];
		}
	}
	while (left <= right) {
		a[right--] = ra[k--];
	}
}
void mergeSort(int left, int right) {
	if (left < right) {
		int mid = (left + right) >> 1;
		mergeSort(left, mid);
		mergeSort(mid + 1, right);
		merge(left, mid, right);
	}
}

希爾排序(4.7s)

插入排序的優化版本,根據每次排序間隔爲gap的所有數字,比如gap爲4是,就會排序0,4,8,12,.../1,5,9,..../2,6,10,..../3,7,11,15,...

const int MAXN = 10000000;
int n;
int a[MAXN];
void shellSort(int left, int right) {
	for (int gap = (left + right) >> 1; gap > 0; gap >>= 1) {
		for (int i = gap; i <= right; i++) {
			for (int j = i - gap; j > 0 && a[j] > a[j + gap]; j -= gap) {
				int tmp = a[j];
				a[j] = a[j + gap];
				a[j + gap] = tmp;
			}
		}
	}
}

堆排序(3.9s)

先建堆,再堆排序,也可以輸入數據的時候就開始建堆,然後再排序

const int MAXN = 10000000;
int n;
int a[MAXN];
void heapSort() {
	int lim = (n >> 1) - 1;
	for (int i = lim; i >= 0; --i) {
		int tmp = i;
		for (int j = (i << 1) | 1; tmp <= lim; j = (tmp << 1) | 1) {
			if (j + 1 < n && a[j] < a[j + 1]) {
				++j;
			}
			if (a[j] < a[tmp]) {
				break;
			}
			int t = a[tmp];
			a[tmp] = a[j];
			a[j] = t;
			tmp = j;
		}
	}
	for (int i = n - 1; i > 0; --i) {
		swap(a[i], a[0]);
		lim = (i >> 1) - 1;
		for (int j = 0, k = 1; j <= lim; k = (j << 1) | 1) {
			if (k + 1 < i && a[k] < a[k + 1]) {
				++k;
			}
			if (a[k] < a[j]) {
				break;
			}
			int t = a[k];
			a[k] = a[j];
			a[j] = t;
			j = k;
		}
	}
}

桶排序(22s)

每個桶的範圍取決於maxn和minn,先將數據放入桶,再排序桶,然後輸出數據

const int MAXN = 10000000;
int n;
int a[MAXN];
void bucketSort() {
	int maxn = 0x80000000;
	int minn = 0x7fffffff;
	for (int i = 0; i < n; ++i) {
		maxn = max(maxn, a[i]);
		minn = min(minn, a[i]);
	}
	int bucketSize = (maxn - minn) / n + 1;
	vector<vector<int> > bucket(bucketSize, vector<int>());
	for (int i = 0; i < n; ++i) {
		int tmp = (a[i] - minn) / n;
		bucket[tmp].push_back(a[i]);
	}
	for (int i = 0; i < bucketSize; ++i) {
		sort(bucket[i].begin(), bucket[i].end());
	}
	int pos = 0;
	for (vector<int> b : bucket) {
		for (int num : b) {
			a[pos++] = num;
		}
	}
}

計數排序(100ms)

有侷限性,cnt數組大小取決於數組最大值,因爲是隨機數,最大值爲0x7fff所以選擇了0x8000作爲數組大小

方法就是建立了一個直方圖數組,如果學過稀疏矩陣的快速轉置方法就很容易理解了,cnt數組記錄的是比當前元素的元素值小(大)的元素個數,計數排序的穩定排序是因爲第三個循環是反向遍歷的,如果是正向遍歷就不是穩定排序了

const int MAXN = 10000000;
int n;
int a[MAXN];
int b[MAXN];
int cnt[0x8000];
void countSort() {
	for (int i = 0; i < MAXN; ++i) {
		++cnt[a[i]];
	}
	for (int i = 1; i < 0x8000; ++i) {
		cnt[i] += cnt[i - 1];
	}
	for (int i = n - 1; i >= 0; --i) {
		b[--cnt[a[i]]] = a[i];
	}
	for (int i = 0; i < n; ++i) {
		a[i] = b[i];
	}
}

基數排序(1.7s)

計數排序優化版本

基數排序不被數據範圍所限制,所以降低了一點速度

const int MAXN = 10000000;
const int BIT = 65536;
int n;
int a[MAXN];
int cnt[BIT];
int b[MAXN];
void radixSort() {
	for (int d = 0; d < 2; ++d) {
		int shift = d << 2;
		for (int i = 0; i < BIT; ++i) {
			cnt[i] = 0;
		}
		for (int i = 0; i < n; ++i) {
			++cnt[(a[i] >> shift) & (0xffff)];
		}
		for (int i = 1; i < BIT; ++i) {
			cnt[i] += cnt[i - 1];
		}
		for (int i = n - 1; i >= 0; --i) {
			b[--cnt[(a[i] >> shift) & (0xffff)]] = a[i];
		}
		swap(a, b);
	}
}

鬆式基排(3.4s)

基數排序優化版本

空間更少,時間就長了一點,但是很穩定

const int MAXN = 10000000;
const int BIT = 256;
int n;
int a[MAXN];
int cnt[BIT];
int b[MAXN];
void radixSort() {
	for (int d = 0; d < 4; ++d) {
		int shift = d << 3;
		for (int i = 0; i < BIT; ++i) {
			cnt[i] = 0;
		}
		for (int i = 0; i < n; ++i) {
			++cnt[(a[i] >> shift) & (0xff)];
		}
		for (int i = 1; i < BIT; ++i) {
			cnt[i] += cnt[i - 1];
		}
		for (int i = n - 1; i >= 0; --i) {
			b[--cnt[(a[i] >> shift) & (0xff)]] = a[i];
		}
		swap(a, b);
	}
}

TimSort(1.5s)

歸併排序和插入排序的結合,先利用插入排序求解每一個小塊(稱爲RUN,一般等於32或者64),然後再用歸併排序中的merge函數逐漸合併這些小塊

const int MAXN = 10000000;
const int RUN = 32;
int ra[MAXN];
int a[MAXN];
int n;
inline void insertionSort(int left, int right) {
	for (int i = left + 1; i < right; i++) {
		int temp = a[i];
		int j = i - 1;
		while (j >= left && a[j] > temp) {
			a[j + 1] = a[j];
			j--;
		}
		a[j + 1] = temp;
	}
}
inline void merge(int lo, int mid, int hi) {
	int i = lo;
	int j = mid;
	int k = lo;
	while (i < mid && j < hi) {
		if (a[i] <= a[j]) {
			ra[k++] = a[i++];
		}
		else {
			ra[k++] = a[j++];
		}
	}
	while (i < mid) {
		ra[k++] = a[i++];
	}
	while (j < hi) {
		ra[k++] = a[j++];
	}
	for (int pos = lo; pos < hi; ++pos) {
		a[pos] = ra[pos];
	}
}
void timSort() {
	for (int i = 0; i < n; i += RUN) {
		insertionSort(i, min((i + RUN), n));
	}
	for (int size = RUN; size < n;) {
		int sz = size << 1;
		for (int left = 0; left < n; left += sz) {
			int right = min((left + sz), n);
			int mid = left + size;
			merge(left, mid, right);
		}
		size = sz;
	}
}

 

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