[排序算法] 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() 函数,真——被吊打…感兴趣者自行测试…

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