[排序算法] 13. 常見排序算法總結及運用高精度計時模板測試性能(複雜度分析、高精度計時、總結)

1. 排序算法總結

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
這些高度總結的圖也是將排序算法進行了一個深刻的總結,在此不必過分多說。

基於學習角度,肯定是首先考慮學習一些簡單的排序算法,如直接插入排序、二分插入排序,再者就希爾排序、堆排序等中級排序算法,然後就是排序算法的精華所在:快速排序、歸併排序等高級排序算法。這也是一個循序漸進的過程,理解每一個排序算法的細節,能否進行手撕代碼、複雜度分析算是對基本的排序算法有所掌握。但是,我們學習算法的目的是爲了應用到實際問題中,幫助我們快速解決問題,這也是前半段學習排序算法的不足之處。

所以這篇博文是作爲一個承前啓後的作用,總結前半段最爲基礎的排序算法的基本知識,併爲後面的 OJ 習題做鋪墊,力求熟練掌握排序算法。

2. 高精度計時器配合隨機數進行性能測試

高精度計時.h 你沒看錯,就是中文的變量、類命名~~~

#include <windows.h>

class 高精度計時
{
public:
	高精度計時(void)
	{
		QueryPerformanceFrequency(&CPU頻率);
	}

	~高精度計時(void) {}

	void 開始()
	{
		QueryPerformanceCounter(&開始時間);
	}

	void 結束()
	{
		QueryPerformanceCounter(&結束時間);

		間隔 = ((double)結束時間.QuadPart - (double)開始時間.QuadPart) / (double)CPU頻率.QuadPart;
	}

	double 間隔毫秒() const
	{
		return 間隔 * 1000;
	}

private:
	double 間隔;

	LARGE_INTEGER 開始時間;

	LARGE_INTEGER 結束時間;

	LARGE_INTEGER CPU頻率;
};

隨機數測試函數:

#define	SIZE (5 * 1000)
void TestSpeed() {
	int array[SIZE];
	int size = SIZE;

	srand(20190118);
	for (int i = 0; i < size; i++) {
		array[i] = rand() % size;
	}
	QuickSort(array,0, size);

	高精度計時	計時器;
	計時器.開始();
	HeapSort(array, size);
	計時器.結束();

	printf("耗時: %f 毫秒\n", 計時器.間隔毫秒());
}

sort.h 這些天來的所有排序算法源代碼:

#include <iostream>
#include <assert.h>
#include "高精度計時.h"

using namespace std;

void PrintArray(int array[], int size) {
	for (int i = 0; i < size; ++i) {
		printf("%d ", array[i]);
	}

	printf("\n");
}

void Swap(int *x, int *y) {
	int tmp = *x;
	*x = *y;
	*y = tmp;
	//*x = ((*x) ^ (*y));
	//*y = ((*x) ^ (*y));
	//*x = ((*x) ^ (*y));
}

// 直接插入排序(遞增)
void InsertSort(int array[], int size) {
	for (int i = 1; i < size; ++i) {	// 遍歷array
		int k = array[i];
		int j = i - 1;
		for (j; j >= 0; --j) {		// 與array[i]前面各個元素比較
			if (array[j] <= k) {	// 如果比前一個元素大,說明前半段已經有序,返回
				break;
			}
			array[j + 1] = array[j];	// 將大於array[i]的元素向後移動,注意從後向前賦值
		}

		array[j + 1] = k;	// 在前半段有序處插入array[i]
	}
}

// 二分插入排序(遞增)
void BinInsertSort(int array[], int size) {
	for (int i = 1; i < size; ++i) {	// 遍歷array
		if (array[i] < array[i - 1]) {
			int k = array[i];
			int j = i - 1;
			int left = 0, right = i - 1, mid = 0;	// 直接對前段數據進行二分查找,找到array[i]應有的位置
			while (left <= right) {
				mid = (left + right) / 2;	// 取中間位置
				if (k < array[mid]) {
					right = mid - 1;	// 插入點在左半區
				}
				else {
					left = mid + 1;		// 插入點在右半區
				}
			}							// 找到位置 right

			for (j; j >= right + 1; --j) {
				array[j + 1] = array[j];	// 將大於array[i]的元素向後移動,注意從後向前賦值
			}

			array[right + 1] = k;	// 在前半段有序處插入array[i]
		}
	}
}

// 希爾排序(遞增)
void ShellSort(int array[], int size) {
	int gap = size;
	while (1) {
		gap = (gap / 3) + 1;
		for (int i = gap; i < size; i++) {
			int k = array[i];
			int j;
			for (j = i - gap; j >= 0; j -= gap) {
				if (array[j] <= k) {
					break;
				}

				array[j + gap] = array[j];
			}

			array[j + gap] = k;
		}
		if (gap == 1) {
			break;
		}
	}
}

// 冒泡排序(遞增)
void BubbleSort(int array[], int size) {
	for (int i = 1; i < size; ++i) {
		int sorted = 1;
		for (int j = 0; j < size - i; ++j) {	// 將剩餘數據的最大值歸位
			if (array[j] > array[j + 1]) {
				int tmp = array[j];
				array[j] = array[j + 1];
				array[j + 1] = tmp;
				sorted = 0;
			}
		}
		if (sorted == 1) {		// 在一趟中沒有排序,則算法結束
			break;
		}
	}
}

// 快速排序(遞增)
// 三數取中法
int BaseNumber(int array[], int begin, int end) {
	int mid = begin + ((end - begin) >> 1);		// 採用位運算效率高
	if (array[begin] > array[mid]) {		// 邏輯判斷取出中位數的下標,當然排序及其它方法均可
		if (array[begin] > array[end]) {
			if (array[mid] > array[end]) {
				return mid;
			}
			else {
				return end;
			}
		}
		else {
			return begin;
		}
	}
	else {
		if (array[mid] > array[end]) {
			if (array[begin] > array[end]) {
				return begin;
			}
			else {
				return end;
			}
		}
		else {
			return mid;
		}
	}
}

// 快速排序(遞增)
// hoare版本
int QuickSort1(int array[], int begin, int end) {
	// 找基準
	// 由於每次規劃取到最大值或最小值的概率都非常高,
	// 這樣容易使樹變成單支樹,所以採用三數取中法來降低取到最值的概率
	//int index = BaseNumber(array, begin, end);	// 基準值在數組中的下標
	//if (index != end) {
	//	Swap(&array[index], &array[end]);	// 將基準值與最後一個數字進行值交換
	//}
	// 基準值
	int key = array[end];

	// 基準值的下標
	int k = end;

	// 兩個指針、begin從0開始,end從size-1開始
	while (begin != end) {
		// begin向後移動,找比基準值大的元素,且begin不能大於end
		// 如果array[begin]比key小,則++begin
		while (array[begin] <= key && (begin < end)) {
			++begin;
		}

		// end向前移動,找比基準值key小的元素,且end不能小於begin
		// 如果array[end]比key大,則--end
		while (array[end] >= key && (begin < end)) {
			--end;
		}

		// 如果下標begin和下標end不相等,則交換所對應的數組元素值
		if (begin != end) {
			Swap(&array[begin], &array[end]);
		}
	}

	// 如果begin的最終位置就是基準的位置則不用交換
	if (begin != k) {
		// 將基準值挪到相應位置上
		Swap(&array[begin], &array[k]);
	}
	return begin;
}

// 快速排序(遞增)
// 挖坑法
int QuickSort2(int array[], int begin, int end) {
	// 依舊三數取中法確定基準
	int index = BaseNumber(array, begin, end);
	if (index != end) {
		Swap(&array[index], &array[end]);
	}

	// 第一個坑
	int key = array[end];
	int k = end;
	while (begin != end) {
		// begin從左邊開始找比關鍵字大的元素將其入坑
		// begin所在位置變爲坑
		while (array[begin] <= key && begin < end) {
			++begin;
		}
		if (begin != end) {
			array[end] = array[begin];
			--end;
		}
		// end從右開始找比關鍵字小的元素將其入begin坑
		while (array[end] >= key && begin < end) {
			--end;
		}
		if (begin != end) {
			array[begin] = array[end];
			++begin;
		}
	}
	if (begin != k) {
		array[begin] = key;
	}

	return begin;
}

// 快速排序(遞增)
// 雙指針方法
int QuickSort3(int array[], int begin, int end) {
	int index = BaseNumber(array, begin, end);
	int cur = begin, prev = begin - 1;
	if (index != end) {
		Swap(&array[index], &array[end]);
	}
	int key = array[end];
	// cur不能超過序列長度
	while (cur <= end) {
		if (array[cur] <= key && ++prev != cur) {
			Swap(&array[cur], &array[prev]);
		}
		++cur;
	}
	return prev;
}

// 快排遞歸實現(遞增)
void QuickSort(int array[], int left, int right) {
	/*
	// 由於快速排序是遞歸調用,容易產生棧溢出
	// 但是快速排序排到最後元素也接近有序,則採用插入排序
	if (right - left < 2) {
		InsertSort(array + left, right - left);
	}
	*/

	// 數據較小時,直接判斷即可
	if (left == right) {
		return;		// 區間內只有一個數
	}
	if (left > right) {
		return;		// 區間內沒有數
	}

	// 基準值是array[right]
	int pos;
	pos = QuickSort1(array, left, right - 1);	// 僅修改QuickSort1,1,2,3即可完成測試
	QuickSort(array, 0, pos);	// 快速排序基準值左側
	QuickSort(array, pos + 1, right);	//快速排序基準值右側

}

// 快排非遞歸實現(遞增)
// 模擬實現棧
typedef struct Stack {
	int *data;
	int size;
}stack;

void InitStack(stack *s) {
	int *data = (int*)malloc(20 * sizeof(int));
	if (data == NULL) {
		assert(0);
		return;
	}
	s->data = data;
	s->size = 0;
}

void PushStack(stack *s, int d) {
	assert(s);
	if (s->size > 20) {
		return;
	}
	else {
		s->data[s->size++] = d;

	}
}

void PopStack(stack *s) {
	assert(s);
	if (s->size == 0) {
		return;
	}
	else {
		s->size--;
	}
}

int TopStack(stack *s) {
	assert(s);
	return s->data[s->size - 1];
}

int EmptyStack(stack *s) {
	assert(s);
	return s->size == 0;
}

// 快排非遞歸實現(遞增)
void QuickSortStack(int array[], int size) {
	stack s;
	int pos, left = 0, right = 0;
	InitStack(&s);
	PushStack(&s, 0);
	PushStack(&s, size - 1);
	while (!EmptyStack(&s)) {
		right = TopStack(&s);
		PopStack(&s);
		left = TopStack(&s);
		PopStack(&s);
		if (left >= right) {
			continue;
		}
		else {
			pos = QuickSort1(array, left, right);
			//先快排基準左側,則先將後側的下標入棧
			if ((right - left) > pos + 1) {
				PushStack(&s, pos + 1);
				PushStack(&s, right - left);
			}
			if (pos > 0) {
				PushStack(&s, 0);
				PushStack(&s, pos - 1);
			}
		}
	}
}

// 直接選擇排序(遞增)
void SelectSort(int array[], int size) {
	for (int i = 0; i < size; ++i) {
		// [0, size - i)
		int m = 0;
		for (int j = 0; j < size - i; ++j) {
			if (array[j] > array[m]) {
				m = j;
			}
		}
		// m 就是最大數的下標了
		//if (array + m == array + size - i - 1)
		//	continue;
		Swap(array + m, array + size - i - 1);
	}
}

// 升級版直接選擇排序,一次即找最大的,也找最小的
void SelectSortOP(int array[], int size) {
	int minSpace = 0;			// 用來放找到的最小數的下標
	int maxSpace = size - 1;	// 用來放找到的最大數的下標

	// 因爲是閉區間,minSpace == maxSpace 時
	// [minSpace, maxSpace] 區間內只剩一個數了
	// 所有可以停止了
	while (minSpace < maxSpace) {
		int min = minSpace;	// 假設最小的是 minSpace 位置
		int max = minSpace; // 假設最大的是 minSpace 位置
		// 在 [minSpace + 1, maxSpace] 區間裏找真正的最小和最大

		for (int j = minSpace + 1; j <= maxSpace; j++) {
			if (array[j] < array[min]) {
				min = j;
			}

			if (array[j] > array[max]) {
				max = j;
			}
		}

		// min 和 max 分別時最小和最大的數的下標
		// 先交換小的
		{
			int t = array[min];
			array[min] = array[minSpace];
			array[minSpace] = t;
		}
		// 再交換大的
		if (max == minSpace) {
			max = min;
		}
		{
			int t = array[max];
			array[max] = array[maxSpace];
			array[maxSpace] = t;
		}

		minSpace++;
		maxSpace--;
	}
}

// 堆排序(遞增)
// 大頂堆的向下調整
void AdjustDown(int array[], int size, int r) {
	int left = 2 * r + 1;
	int right = 2 * r + 2;
	if (left >= size) {		// 是否爲葉子節點
		return;			// 葉子節點直接結束
	}
	int m = left;		// 有左孩子
	// 是不是有右孩子,並找最大的孩子
	if (right < size && array[right] > array[left]) {
		m = right;
	}
	// 如果根的值大於最大孩子,直接返回
	if (array[r] >= array[m]) {
		return;
	}

	Swap(array + r, array + m);	 // 將最大值最爲新的根
	AdjustDown(array, size, m);	 // 遞歸向下調整
}

// 建堆
void CreateHeap(int array[], int size) {
	// i=最後一個非葉子節點
	// 已知parent,則 left=2* parent+1,right=2*parent+2
	// 已知child,則parent=(child-1)/2,在此均爲數組的下標
	// 故最後一個非葉子節點,就是數組最後一個下標size-1,再-1,結果除2即可
	for (int i = (size - 1 - 1) / 2; i >= 0; --i) {
		AdjustDown(array, size, i);
	}
}

// 堆排序(遞增)
void HeapSort(int array[], int size) {
	CreateHeap(array, size);

	for (int i = 0; i < size; ++i) {
		Swap(array, array + size - i - 1);
		AdjustDown(array, size - i - 1, 0);
	}
}

// 歸併排序(遞增)
// 合併兩個有序序列
void Merge(int array[], int left, int mid, int right, int extra[]) {
	int size = right - left;
	int left_index = left;
	int right_index = mid;
	int extra_index = 0;

	while (left_index < mid && right_index < right) {
		if (array[left_index] <= array[right_index]) {
			extra[extra_index] = array[left_index];
			++left_index;
		}
		else {
			extra[extra_index] = array[right_index];
			++right_index;
		}

		extra_index++;
	}

	while (left_index < mid) {
		extra[extra_index++] = array[left_index++];
	}

	while (right_index < right) {
		extra[extra_index++] = array[right_index++];
	}

	for (int i = 0; i < size; i++) {
		array[left + i] = extra[i];
	}
}

void __MergeSort(int array[], int left, int right, int extra[]) {
	// 終止條件
	if (right == left + 1) {
		// 只剩一個數
		return;
	}

	if (right <= left) {
		// 區間內沒有數了
		return;
	}

	int mid = left + (right - left) / 2;
	// [left, mid)	[mid, right)
	__MergeSort(array, left, mid, extra);
	__MergeSort(array, mid, right, extra);
	// 左右區間都有序
	Merge(array, left, mid, right, extra);
}

void MergeSortNor(int array[], int size) {
	int *extra = (int *)malloc(sizeof(int)* size);

	for (int i = 1; i < size; i = i * 2) {
		for (int j = 0; j < size; j = j + 2 * i) {
			int left, mid, right;
			left = j;
			mid = j + i;
			right = mid + i;
			if (mid >= size) {
				// 沒有右邊的區間 [mid, right)
				continue;
			}

			if (right > size) {
				right = size;
			}

			Merge(array, left, mid, right, extra);
		}
	}

	free(extra);
}

// 歸併排序(遞增)
void MergeSort(int array[], int size) {
	int *extra = (int *)malloc(sizeof(int)* size);
	__MergeSort(array, 0, size, extra);
	free(extra);
}

// 桶排序(遞增)
#define BUCKSIZE	100
typedef struct {
	int data[BUCKSIZE];
	int count;		// 桶中元素的個數
} BuckType;			// 桶類型

void BucketSort(int array[], int size) {
	int max, min, num, pos;
	BuckType *pB;
	max = min = array[0];
	for (int i = 1; i < size; ++i) {
		if (array[i] > max) {
			max = array[i];
		}
		else if (array[i] < min) {
			min = array[i];
		}
	}
	num = (max - min + 1) / 10 + 1;		// 求出桶的個數
	pB = (BuckType*)malloc(sizeof(BuckType) * num);
	memset(pB, 0, sizeof(BuckType) * num);

	for (int i = 0; i < size; ++i) {			// 將數組元素分配進桶
		int k = (array[i] - min + 1) / BUCKSIZE;	// 求出array[i]對應的桶號,在此爲10個桶
		(pB + k)->data[(pB + k)->count] = array[i];
		(pB + k)->count++;
	}
	pos = 0;
	for (int i = 0; i < num; ++i) {
		QuickSort((pB + i)->data, 0, (pB + i)->count);  // 單個桶快速排序
		for (int j = 0; j < (pB + i)->count; ++j) {
			array[pos++] = (pB + i)->data[j];
		}
	}
}

// 基數排序(遞增)
// 動態二維數組實現
int Maxbit(int array[], int size) {		// 求待排序序列最大元素位數
	int maxvalue = array[0], digits = 0;	// 初始化最大元素爲array[0],最大位數爲0
	for (int i = 1; i < size; i++) {	// 找到序列中最大元素
		if (array[i] > maxvalue)
			maxvalue = array[i];
	}
	while (maxvalue != 0) {		// 分解得到最大元素的位數
		digits++;
		maxvalue /= 10;
	}
	return digits;
}

int Bitnumber(int x, int bit) {		// 求x第bit位上的數字,例如238第2位上的數字爲3
	int temp = 1;
	for (int i = 1; i < bit; ++i) {
		temp *= 10;
	}
	return (x / temp) % 10;
}

// 基數排序(遞增)
// 動態二維數組實現
void RadixSort(int array[], int size) {
	int i, j, k, bit, maxbit;
	maxbit = Maxbit(array, size);	//求最大元素位數
	cout << "最大元素位數爲:" << maxbit << "位 " << endl;
	int **B = new int *[10];	// 分配二維動態數組
	for (i = 0; i < 10; ++i)
		B[i] = new int[size + 1];	// 每個桶都是size+1個空間,其中每個桶的第一個位置即B[0]第0位存放元素個數
	for (i = 0; i < 10; i++)
		B[i][0] = 0;	// 統計第i個桶的元素個數
	// 從個位到高位,對不同的位數進行桶排序
	for (bit = 1; bit <= maxbit; ++bit) {
		for (j = 0; j < size; j++) {	// 分配 
			int num = Bitnumber(array[j], bit);	// 取array[j]第bit位上的數字
			int index = ++B[num][0];
			B[num][index] = array[j];
		}
		for (i = 0, j = 0; i < 10; ++i) {	// 收集
			for (k = 1; k <= B[i][0]; ++k)
				array[j++] = B[i][k];
			B[i][0] = 0;	// 收集後元素個數置零
		}
	}
	for (int i = 0; i < 10; i++)
		delete[]B[i];
	delete B;
}

// 基數排序(遞增)
// 一維數組實現
const int maxn = 1000;
int a[maxn], size;
int maxbit(int array[], int size) { //輔助函數,求數據的最大位數
	int d = 1;//統計最大的位數
	int p = 10;
	for (int i = 0; i < size; ++i) {
		while (array[i] >= p) {
			p *= 10;
			++d;
		}
	}
	return d;
}

// 基數排序(遞增)
// 一維數組實現
void radixsort(int array[], int size) {
	int d = maxbit(array, size); // 求最大位數 
	int *tmp = new int[size]; // 輔助數組 
	int *count = new int[10]; // 計數器
	int i, j, k;
	int radix = 1;
	for (i = 1; i <= d; i++) { // 進行d次排序
		for (j = 0; j < 10; ++j) {
			count[j] = 0; // 每次分配前清空計數器
		}
		for (j = 0; j < size; ++j) {
			k = (array[j] / radix) % 10; // 取出個位數,然後是十位數,... 
			count[k]++;  // 統計每個桶中的記錄數
		}
		for (j = 1; j < 10; ++j) {
			count[j] += count[j - 1]; // 將tmp中的位置依次分配給每個桶
		}
		for (j = size - 1; j >= 0; --j) { //將所有桶中記錄依次收集到tmp中
			k = (array[j] / radix) % 10;
			tmp[--count[k]] = array[j];
		}
		for (j = 0; j < size; j++) {	// 將臨時數組的內容複製到array中
			array[j] = tmp[j];
		}
		cout << "第" << i << "次排序結果:" << endl;
		for (int i = 0; i < size; ++i)
			cout << array[i] << "   ";
		cout << endl;
		radix = radix * 10;
	}
	delete[]tmp;
	delete[]count;
}

// 測試函數
void TestRight() {
	int array[] = { 3, 9, 1, 4, 2, 8, 2, 7, 5, 3, 6, 11, 9, 4, 2, 5, 0, 6 };
	int size = sizeof(array) / sizeof(int);

	SelectSortOP(array, size);

	PrintArray(array, size);
}


#define	SIZE (5 * 1000)
void TestSpeed() {
	int array[SIZE];
	int size = SIZE;

	srand(20190118);
	for (int i = 0; i < size; i++) {
		array[i] = rand() % size;
	}
	// 需要有序序列時可調用
	// QuickSort(array,0, size);

	高精度計時	計時器;
	計時器.開始();
	HeapSort(array, size);
	計時器.結束();

	printf("耗時: %f 毫秒\n", 計時器.間隔毫秒());
}

main.cpp主函數:

#include "sort.h"

int main() {
	TestRight();
	TestSpeed();

	system("pause");

	return 0;
}

3. 性能測試

有序 亂序 逆序
冒泡 0.2ms 7.9s
插排 0.2ms 1.7s
希爾 2.3ms 10ms
選擇 3.4s 3.3s
選擇OP 4.7s 2s
堆排序 9ms 15ms

測了測庫的 sort() 函數,真——被吊打…感興趣者自行測試…

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