系统掌握——九大常用排序算法

1、九大常用排序算法介绍

        在程序设计中,经常需要用到排序,可能大家都对排序算法有过一定的了解,不过,如果能系统地掌握它们,绝对是一件非常好的事情。

        可能我们很熟悉冒泡排序、选择排序、快速排序、插入排序等等之类的排序算法,当被问到堆排序、归并排序等等就不知道怎么回事了。那么我们怎么去系统地记忆这些常用的排序算法呢?

        我们可以根据排序的方式不同将排序算法分成插入排序、交换排序、选择排序、归并排序和基数排序,插入排序包含了直接插入排序、折半插入排序;交换排序包含了冒泡排序、快速排序;选择排序包含了直接选择排序、堆排序;可以结构图来记忆常用的这些排序算法,结构图如下:


2、算法的性能对比

3、算法的执行步骤与代码实现

(1)直接插入排序

/*
直接插入排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)选取无序区中第一个元素i
(2)将i与有序区中元素j(0 <= j <= i-1)比较,寻找插入位置,同时移动元素
(3)将该元素插入合适位置
(4)重复(1)(2)(3)直至无序区长度为0
*/
template<typename T>
void InsertSort(T a[], int n)
{
	for (int i = 1; i < n; i++) //每次选取无序区间的第一个元素插入
	{
		T t = a[i];
		int j = i - 1; //i - 1即为有序区最后一个元素
		while (j >= 0 && t < a[j]) //边比较寻找插入位置边移动元素
		{
			a[j + 1] = a[j];
			j--;
		}
		a[j + 1] = t; //插入元素到有序区
	}
}

(2)折半插入排序

/*
折半插入排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)选取无序区中第一个元素i
(2)在有序区间折半搜索该元素应该插入的位置
(3)将该元素插入合适位置
(4)重复(1)(2)(3)直至无序区长度为0
*/
template<typename T>
void BinaryInsertSort(T a[], int n)
{
	for (int i = 1; i < n; i++) //每次选取无序区间的第一个元素插入
	{
		T t = a[i];
		int left = 0, right = i - 1; //折半寻找插入位置
		while (left <= right)
		{
			int mid = (left + right) / 2; //取区间中间位置
			if (t < a[mid]) right = mid - 1; //插入点在左半区
			else left = mid + 1; //插入点在右半区(为保证排序稳定性,如果有元素相等,则在该元素后插入)
		}
		int j = i - 1;
		while (j >= right + 1) //移动元素 
		{
			a[j + 1] = a[j];
			j--;
		}
		a[j + 1] = t; //插入元素到有序区
	}
}

(3)希尔排序

详细排序思路参考:https://www.cnblogs.com/chengxiao/p/6104371.html

/*
希尔排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)将待排序集合分成k个组,且每个组元素下标的差值为k
(2)对每个组的元素进行直接插入排序
(3)减少组的规模(这里采用折半减少的方式),重复(1)(2)直至规模为最小值1
*/
template<typename T>
void ShellSort(T a[], int n)
{
	int k = n / 2; //排序划分组数
	while (k > 0)
	{
		for (int g = 0; g < k; g++) //对每组的元素分别进行插入排序
		{
			for (int i = g + k; i < n; i += k) //插入排序
			{
				T t = a[i];
				int j = i - k; //将直接插入排序中的1换成k
				while (j >= 0 && t < a[j])
				{
					a[j + k] = a[j];
					j -= k;
				}
				a[j + k] = t;
			}
		}
		k = k / 2; //减少分组数,直至分组为0
	}
}

(4)冒泡排序

/*
冒泡排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)从无序区间的最后一个元素开始,如果该元素小于它相邻的前一个元素,则两者交换,重复比较直至无序区所有元素都比较完成
(2)将无序区间的第一个元素置入有序区间
(3)重复(1)(2)直至无序区间长度为0
*/
template <typename T>
void BubbleSort(T a[], int n)
{
	for (int i = 0; i < n; i++) //区间[0, i]为有序区
	{
		for (int j = n - 1; j > i; j--) //从无序区间的最后一个元素开始
		{
			if (a[j] < a[j - 1]) //如果该元素小于它相邻的前一个元素,则两者交换
			{
				T t = a[j];
				a[j] = a[j - 1];
				a[j - 1] = t;
			}
		}
	}
}

(5)快速排序

/*
快速排序
a:待排序元素集合
left:集合中第一个元素下标
right:集合中最后一个元素下标
步骤:
(1)以集合第一个元素为基准,进行划分,将小与它的元素放在它左边,将大于它的元素放在它右边
(2)将左边元素的区间、右边元素的区间递归进行划分
(3)重复(1)(2),直至子区间不能再划分为止
*/
template <typename T>
void QuickSort(T a[], int left, int right)
{
	if (left >= right) return;
	//以第一个元素为基准,执行一趟划分
	int i = left, j = right;
	T t = a[left];
	while (i < j)
	{
		while (i < j && a[j] >= t) //从右向左扫描,选取第一个小于t的元素交换
			j--;
		a[i] = a[j];
		while (i < j && a[i] <= t) //再从左往右扫描,选取第一个大于t的元素交换
			i++;
		a[j] = a[i];
	}
	a[i] = t;
	//递归执行划分
	QuickSort(a, left, i - 1);
	QuickSort(a, i + 1, right);
}

(6)直接选择排序

/*
直接选择排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)从无序区间选择最小的元素置入有序区间
(2)重复(1)直至无序区间长度为0
*/
template<typename T>
void SelectSort(T a[], int n)
{
	for (int i = 0; i < n; i++)
	{
		int imin = i; //标记无序区间最小元素下标
		for (int j = i + 1; j < n; j++) //选出无序区间的最小元素
			if (a[j] < a[imin]) imin = j;
		if (imin != i) //将选出的最小元素和无序区第一个元素交换
		{
			T t = a[i];
			a[i] = a[imin];
			a[imin] = t;
		}
	}
}

(7)堆排序

详细排序思路参考:https://www.cnblogs.com/chengxiao/p/6129630.html

/*
调整堆:给定一个非叶子节点,递归向下调整,使其满足堆的结构(根节点大于它的左右孩子节点)
a:待排序元素集合
n:非叶子节点的下标
n:集合中元素的个数
步骤:
(1)若左右孩子节点的最大值大于当前节点,将其和最大值的节点交换
(2)交换后可能会导致子树不满足堆的定义,所以要递归进行调整
*/
template<typename T>
void AdjustHeap(T a[], int i, int n)
{
	T t = a[i];
	int j = i * 2 + 1; //j指向i的左孩子节点
	while (j < n)
	{
		if (j + 1 < n && a[j] < a[j + 1]) j++; //如果右子树大于它的左子树,则j改为指向右子树
		if (t < a[j]) a[(j - 1) / 2] = a[j]; //如果孩子节点较大,则将孩子节点与父节点交换(因为是递归调整,所以不用修改孩子节点的值)
		else break;
		j = j * 2 + 1; //递归遍历孩子节点
	}
	j < n ? a[j] = t : a[(j - 1) / 2] = t; //递归出口,被break掉的情况和j越界的情况
}

/*
堆排序 
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)建立堆
(2)每次取无序区堆顶元素,置入有序区
(3)调整堆
(4)重复(2)(3)直至无序区长度为0
*/
template<typename T>
void HeapSort(T a[], int n)
{
	//构建堆
	for (int i = n / 2 - 1; i >= 0; i--) //从第一个非叶子节点开始,向上调整堆直至调整至堆顶
		AdjustHeap(a, i, n);
	//堆排序
	for (int i = n - 1; i >= 0; i--) //每次选取堆顶元素置入有序区
	{
		T t = a[0];
		a[0] = a[i];
		a[i] = t;
		AdjustHeap(a, 0, i); //调整堆
	}
}

(8)归并排序

详细排序思路参考:https://www.cnblogs.com/chengxiao/p/6194356.html

/*
归并排序 
a:待排序元素集合
left:集合中第一个元素下标
right:集合中最后一个元素下标
步骤:
(1)将排序集合递归划分成两个集合,直到划分集合的长度为1
(2)以有序的方式合并两个有序集合
(3)重复(2)直到所有集合都合并完
*/
template<typename T>
void MergeSort(T a[], int left, int right)
{
	if (left < right)
	{
		//分
		int mid = (left + right) / 2; //二分法将集合划分成两个部分
		MergeSort(a, left, mid); //递归划分左边集合
		MergeSort(a, mid + 1, right); //递归划分右边集合

		//合
		T *tmp = new T[right - left + 1]; //建立一个临时数组,存放合并的有序序列
		int i = left, j = mid + 1, k = 0; //i为左边集合下标,j为右边集合下标,k为合并集合下标
		while (i <= mid && j <= right)
		{
			if (a[i] < a[j]) tmp[k++] = a[i++]; //左边集合值更小
			else tmp[k++] = a[j++]; //右边集合值更小
		}
		while (i <= mid) tmp[k++] = a[i++]; //如果左边集合未遍历完
		while (j <= right) tmp[k++] = a[j++]; //如果右边集合未遍历完
		while (--k >= 0) a[left + k] = tmp[k]; //将临时数组拷贝回原数组
		delete[] tmp; //释放空间
	}
}

(9)基数排序

/*
基数节点
val:元素的值,采用字符串表示
next:该元素的下一个节点地址
*/
#include <string>
typedef struct _RadixNode
{
	string val;
	struct _RadixNode *next;
}RadixNode;

/*
基数排序
p:待排序元素集合,用单链表表示
r:元素的基数(对于二进制r为2,对于10进制r为10)
d:元素的位数
步骤:
(1)将集合分成r个组,r的值为集合每一位的所有可能取值的个数
(2)从集合的第d位开始,重复(3)(4)直至d的值为0,d的值为集合中元素的最大位数
(3)将集合中所有元素分配到相应的组
(4)从所有组中收集元素
*/
void RadixSort(RadixNode *&p, int r, int d)
{
	RadixNode **head = new RadixNode*[r]; //存储基数的每一个字符的头节点
	RadixNode **tail = new RadixNode*[r]; //存储基数的每一个字符的尾节点
	while (--d >= 0) //对集合中所有数字的每一位进行分配和收集
	{
		//分配
		for (int i = 0; i < r; i++) //初始化头结点和尾节点
			head[i] = tail[i] = NULL;
		while (p != NULL) //分配集合中的每一个元素
		{
			int i = p->val[d] - '0'; //获取元素第d位对应的字符下标
			if (head[i] == NULL) //分配到对应的字符组,组中没有元素的情况
				head[i] = tail[i] = p;
			else //分配到对应的字符组,组中已经有元素的情况
				tail[i] = tail[i]->next = p;
			p = p->next;
		}
		//收集
		p = NULL;
		auto q = p;
		for (int i = 0; i < r; i++) //收集每一个组的所有元素
		{
			if (head[i] != NULL) //组不为空收集
			{
				if (p == NULL) //还未收集到元素的情况
				{
					p = head[i]; 
					q = tail[i];
				}
				else //已经收集到元素的情况
				{
					q->next = head[i];
					q = tail[i];
				}
			}
		}
		if(q) q->next = NULL; //添加链表结束标志
	}
	delete[] head, tail; //释放空间
}

3、测试

这里作者随机生成了20万个数据,数据的范围是0~10000,对上面提到的所有算法进行了时间复杂度的测试,测试代码和测试结果如下:

测试代码:

#include <Windows.h>
#include <iostream>
using namespace std;

void InitRandSet(int a[], int n, int minVal, int maxVal)
{
	int k = maxVal - minVal + 1;
	for (int i = 0; i < n; i++)
		a[i] = minVal + rand() % k;
}

void InitRadixData(RadixNode *&p, int a[], int n, int d)
{
	auto *q = p;
	for (int i = 0; i < n; i++)
	{
		if (q == NULL) p = q = new RadixNode;
		else q = q->next = new RadixNode;
		q->val = to_string(a[i]);
		q->val = string(d - q->val.length(), '0') + q->val;
	}
	q->next = NULL;
}

int main()
{
	const int n = 200000; //待排序集合长度
	const int minVal = 0; //数据最小值,用于生成随机数据
	const int maxVal = 10000; //数据最大值,用于生成随机数据
	const int r = 10, d = 5; //基数排序参数d,r

	int *a = new int[n]; //待排序集合数组
	int *aCopy = new int[n]; //集合数组备份,因为排序后数组会变为有序,所以需要备份无序数组
	InitRandSet(aCopy, n, minVal, maxVal); //生成随机数组
	RadixNode *p = NULL; //基数排序数据格式
	InitRadixData(p, aCopy, n, d); //基数排序数据初始化

	DWORD pre_time = GetTickCount(); //保存上一个时间
	auto difTime = [&pre_time]()->long { //获取时间差
		DWORD dif = GetTickCount() - pre_time;
		pre_time = GetTickCount();
		return dif;
	};

	auto copySet = [&a, aCopy, n]()->void { //拷贝数组
		for (int i = 0; i < n; i++)
			a[i] = aCopy[i];
	};

	cout << "待排序集合长度:" << n << endl;
	//插入排序
	copySet(); InsertSort(a, n); cout << "1.插入排序:" << difTime() << "ms" << endl;
	copySet(); BinaryInsertSort(a, n); cout << "2.折半插入排序:" << difTime() << "ms" << endl;
	copySet(); ShellSort(a, n); cout << "3.希尔排序:" << difTime() << "ms" << endl;
	//交换排序
	copySet(); BubbleSort(a, n); cout << "4.冒泡排序:" << difTime() << "ms" << endl;
	copySet(); QuickSort(a, 0, n - 1); cout << "5.快速排序:" << difTime() << "ms" << endl;
	//选择排序
	copySet(); SelectSort(a, n); cout << "6.选择排序:" << difTime() << "ms" << endl;
	copySet(); HeapSort(a, n); cout << "7.堆排序:" << difTime() << "ms" << endl;
	//归并排序
	copySet(); MergeSort(a, 0, n - 1); cout << "8.归并排序:" << difTime() << "ms" << endl;
	//基数排序
	RadixSort(p, r, d); cout << "9.基数排序:" << difTime() << "ms" << endl;

	delete[] a, aCopy; //释放资源
	while (p != NULL) { auto q = p; p = p->next; delete q; } //释放链表申请的空间
	
	while (1) cin.get();
	return 0;
}

测试结果:


完整代码:

#include <iostream>
using namespace std;

/*
直接插入排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)选取无序区中第一个元素i
(2)将i与有序区中元素j(0 <= j <= i-1)比较,寻找插入位置,同时移动元素
(3)将该元素插入合适位置
(4)重复(1)(2)(3)直至无序区长度为0
*/
template<typename T>
void InsertSort(T a[], int n)
{
	for (int i = 1; i < n; i++) //每次选取无序区间的第一个元素插入
	{
		T t = a[i];
		int j = i - 1; //i - 1即为有序区最后一个元素
		while (j >= 0 && t < a[j]) //边比较寻找插入位置边移动元素
		{
			a[j + 1] = a[j];
			j--;
		}
		a[j + 1] = t; //插入元素到有序区
	}
}

/*
折半插入排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)选取无序区中第一个元素i
(2)在有序区间折半搜索该元素应该插入的位置
(3)将该元素插入合适位置
(4)重复(1)(2)(3)直至无序区长度为0
*/
template<typename T>
void BinaryInsertSort(T a[], int n)
{
	for (int i = 1; i < n; i++) //每次选取无序区间的第一个元素插入
	{
		T t = a[i];
		int left = 0, right = i - 1; //折半寻找插入位置
		while (left <= right)
		{
			int mid = (left + right) / 2; //取区间中间位置
			if (t < a[mid]) right = mid - 1; //插入点在左半区
			else left = mid + 1; //插入点在右半区(为保证排序稳定性,如果有元素相等,则在该元素后插入)
		}
		int j = i - 1;
		while (j >= right + 1) //移动元素 
		{
			a[j + 1] = a[j];
			j--;
		}
		a[j + 1] = t; //插入元素到有序区
	}
}

/*
希尔排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)将待排序集合分成k个组,且每个组元素下标的差值为k
(2)对每个组的元素进行直接插入排序
(3)减少组的规模(这里采用折半减少的方式),重复(1)(2)直至规模为最小值1
*/
template<typename T>
void ShellSort(T a[], int n)
{
	int k = n / 2; //排序划分组数
	while (k > 0)
	{
		for (int g = 0; g < k; g++) //对每组的元素分别进行插入排序
		{
			for (int i = g + k; i < n; i += k) //插入排序
			{
				T t = a[i];
				int j = i - k; //将直接插入排序中的1换成k
				while (j >= 0 && t < a[j])
				{
					a[j + k] = a[j];
					j -= k;
				}
				a[j + k] = t;
			}
		}
		k = k / 2; //减少分组数,直至分组为0
	}
}

/*
冒泡排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)从无序区间的最后一个元素开始,如果该元素小于它相邻的前一个元素,则两者交换,重复比较直至无序区所有元素都比较完成
(2)将无序区间的第一个元素置入有序区间
(3)重复(1)(2)直至无序区间长度为0
*/
template <typename T>
void BubbleSort(T a[], int n)
{
	for (int i = 0; i < n; i++) //区间[0, i]为有序区
	{
		for (int j = n - 1; j > i; j--) //从无序区间的最后一个元素开始
		{
			if (a[j] < a[j - 1]) //如果该元素小于它相邻的前一个元素,则两者交换
			{
				T t = a[j];
				a[j] = a[j - 1];
				a[j - 1] = t;
			}
		}
	}
}

/*
快速排序
a:待排序元素集合
left:集合中第一个元素下标
right:集合中最后一个元素下标
步骤:
(1)以集合第一个元素为基准,进行划分,将小与它的元素放在它左边,将大于它的元素放在它右边
(2)将左边元素的区间、右边元素的区间递归进行划分
(3)重复(1)(2),直至子区间不能再划分为止
*/
template <typename T>
void QuickSort(T a[], int left, int right)
{
	if (left >= right) return;
	//以第一个元素为基准,执行一趟划分
	int i = left, j = right;
	T t = a[left];
	while (i < j)
	{
		while (i < j && a[j] >= t) //从右向左扫描,选取第一个小于t的元素交换
			j--;
		a[i] = a[j];
		while (i < j && a[i] <= t) //再从左往右扫描,选取第一个大于t的元素交换
			i++;
		a[j] = a[i];
	}
	a[i] = t;
	//递归执行划分
	QuickSort(a, left, i - 1);
	QuickSort(a, i + 1, right);
}

/*
直接选择排序
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)从无序区间选择最小的元素置入有序区间
(2)重复(1)直至无序区间长度为0
*/
template<typename T>
void SelectSort(T a[], int n)
{
	for (int i = 0; i < n; i++)
	{
		int imin = i; //标记无序区间最小元素下标
		for (int j = i + 1; j < n; j++) //选出无序区间的最小元素
			if (a[j] < a[imin]) imin = j;
		if (imin != i) //将选出的最小元素和无序区第一个元素交换
		{
			T t = a[i];
			a[i] = a[imin];
			a[imin] = t;
		}
	}
}



/*
调整堆:给定一个非叶子节点,递归向下调整,使其满足堆的结构(根节点大于它的左右孩子节点)
a:待排序元素集合
n:非叶子节点的下标
n:集合中元素的个数
步骤:
(1)若左右孩子节点的最大值大于当前节点,将其和最大值的节点交换
(2)交换后可能会导致子树不满足堆的定义,所以要递归进行调整
*/
template<typename T>
void AdjustHeap(T a[], int i, int n)
{
	T t = a[i];
	int j = i * 2 + 1; //j指向i的左孩子节点
	while (j < n)
	{
		if (j + 1 < n && a[j] < a[j + 1]) j++; //如果右子树大于它的左子树,则j改为指向右子树
		if (t < a[j]) a[(j - 1) / 2] = a[j]; //如果孩子节点较大,则将孩子节点与父节点交换(因为是递归调整,所以不用修改孩子节点的值)
		else break;
		j = j * 2 + 1; //递归遍历孩子节点
	}
	j < n ? a[j] = t : a[(j - 1) / 2] = t; //递归出口,被break掉的情况和j越界的情况
}

/*
堆排序 
a:待排序元素集合
n:集合中元素的个数
步骤:
(1)建立堆
(2)每次取无序区堆顶元素,置入有序区
(3)调整堆
(4)重复(2)(3)直至无序区长度为0
*/
template<typename T>
void HeapSort(T a[], int n)
{
	//构建堆
	for (int i = n / 2 - 1; i >= 0; i--) //从第一个非叶子节点开始,向上调整堆直至调整至堆顶
		AdjustHeap(a, i, n);
	//堆排序
	for (int i = n - 1; i >= 0; i--) //每次选取堆顶元素置入有序区
	{
		T t = a[0];
		a[0] = a[i];
		a[i] = t;
		AdjustHeap(a, 0, i); //调整堆
	}
}

/*
归并排序 
a:待排序元素集合
left:集合中第一个元素下标
right:集合中最后一个元素下标
步骤:
(1)将排序集合递归划分成两个集合,直到划分集合的长度为1
(2)以有序的方式合并两个有序集合
(3)重复(2)直到所有集合都合并完
*/
template<typename T>
void MergeSort(T a[], int left, int right)
{
	if (left < right)
	{
		//分
		int mid = (left + right) / 2; //二分法将集合划分成两个部分
		MergeSort(a, left, mid); //递归划分左边集合
		MergeSort(a, mid + 1, right); //递归划分右边集合

		//合
		T *tmp = new T[right - left + 1]; //建立一个临时数组,存放合并的有序序列
		int i = left, j = mid + 1, k = 0; //i为左边集合下标,j为右边集合下标,k为合并集合下标
		while (i <= mid && j <= right)
		{
			if (a[i] < a[j]) tmp[k++] = a[i++]; //左边集合值更小
			else tmp[k++] = a[j++]; //右边集合值更小
		}
		while (i <= mid) tmp[k++] = a[i++]; //如果左边集合未遍历完
		while (j <= right) tmp[k++] = a[j++]; //如果右边集合未遍历完
		while (--k >= 0) a[left + k] = tmp[k]; //将临时数组拷贝回原数组
		delete[] tmp; //释放空间
	}
}

/*
基数节点
val:元素的值,采用字符串表示
next:该元素的下一个节点地址
*/
#include <string>
typedef struct _RadixNode
{
	string val;
	struct _RadixNode *next;
}RadixNode;

/*
基数排序
p:待排序元素集合,用单链表表示
r:元素的基数(对于二进制r为2,对于10进制r为10)
d:元素的位数
步骤:
(1)将集合分成r个组,r的值为集合每一位的所有可能取值的个数
(2)从集合的第d位开始,重复(3)(4)直至d的值为0,d的值为集合中元素的最大位数
(3)将集合中所有元素分配到相应的组
(4)从所有组中收集元素
*/
void RadixSort(RadixNode *&p, int r, int d)
{
	RadixNode **head = new RadixNode*[r]; //存储基数的每一个字符的头节点
	RadixNode **tail = new RadixNode*[r]; //存储基数的每一个字符的尾节点
	while (--d >= 0) //对集合中所有数字的每一位进行分配和收集
	{
		//分配
		for (int i = 0; i < r; i++) //初始化头结点和尾节点
			head[i] = tail[i] = NULL;
		while (p != NULL) //分配集合中的每一个元素
		{
			int i = p->val[d] - '0'; //获取元素第d位对应的字符下标
			if (head[i] == NULL) //分配到对应的字符组,组中没有元素的情况
				head[i] = tail[i] = p;
			else //分配到对应的字符组,组中已经有元素的情况
				tail[i] = tail[i]->next = p;
			p = p->next;
		}
		//收集
		p = NULL;
		auto q = p;
		for (int i = 0; i < r; i++) //收集每一个组的所有元素
		{
			if (head[i] != NULL) //组不为空收集
			{
				if (p == NULL) //还未收集到元素的情况
				{
					p = head[i]; 
					q = tail[i];
				}
				else //已经收集到元素的情况
				{
					q->next = head[i];
					q = tail[i];
				}
			}
		}
		if(q) q->next = NULL; //添加链表结束标志
	}
	delete[] head, tail; //释放空间
}

#include <Windows.h>

void InitRandSet(int a[], int n, int minVal, int maxVal)
{
	int k = maxVal - minVal + 1;
	for (int i = 0; i < n; i++)
		a[i] = minVal + rand() % k;
}

void InitRadixData(RadixNode *&p, int a[], int n, int d)
{
	auto *q = p;
	for (int i = 0; i < n; i++)
	{
		if (q == NULL) p = q = new RadixNode;
		else q = q->next = new RadixNode;
		q->val = to_string(a[i]);
		q->val = string(d - q->val.length(), '0') + q->val;
	}
	q->next = NULL;
}

int main()
{
	const int n = 200000; //待排序集合长度
	const int minVal = 0; //数据最小值,用于生成随机数据
	const int maxVal = 10000; //数据最大值,用于生成随机数据
	const int r = 10, d = 5; //基数排序参数d,r

	int *a = new int[n]; //待排序集合数组
	int *aCopy = new int[n]; //集合数组备份,因为排序后数组会变为有序,所以需要备份无序数组
	InitRandSet(aCopy, n, minVal, maxVal); //生成随机数组
	RadixNode *p = NULL; //基数排序数据格式
	InitRadixData(p, aCopy, n, d); //基数排序数据初始化

	DWORD pre_time = GetTickCount(); //保存上一个时间
	auto difTime = [&pre_time]()->long { //获取时间差
		DWORD dif = GetTickCount() - pre_time;
		pre_time = GetTickCount();
		return dif;
	};

	auto copySet = [&a, aCopy, n]()->void { //拷贝数组
		for (int i = 0; i < n; i++)
			a[i] = aCopy[i];
	};

	cout << "待排序集合长度:" << n << endl;
	//插入排序
	copySet(); InsertSort(a, n); cout << "1.插入排序:" << difTime() << "ms" << endl;
	copySet(); BinaryInsertSort(a, n); cout << "2.折半插入排序:" << difTime() << "ms" << endl;
	copySet(); ShellSort(a, n); cout << "3.希尔排序:" << difTime() << "ms" << endl;
	//交换排序
	copySet(); BubbleSort(a, n); cout << "4.冒泡排序:" << difTime() << "ms" << endl;
	copySet(); QuickSort(a, 0, n - 1); cout << "5.快速排序:" << difTime() << "ms" << endl;
	//选择排序
	copySet(); SelectSort(a, n); cout << "6.选择排序:" << difTime() << "ms" << endl;
	copySet(); HeapSort(a, n); cout << "7.堆排序:" << difTime() << "ms" << endl;
	//归并排序
	copySet(); MergeSort(a, 0, n - 1); cout << "8.归并排序:" << difTime() << "ms" << endl;
	//基数排序
	RadixSort(p, r, d); cout << "9.基数排序:" << difTime() << "ms" << endl;

	delete[] a, aCopy; //释放资源
	while (p != NULL) { auto q = p; p = p->next; delete q; } //释放链表申请的空间
	
	while (1) cin.get();
	return 0;
}

参考资料:

[1] 李春葆.数据结构教程.清华大学出版社,2013.

[2]dreamcatcher-cx.图解排序算法.博客园.

 

猜你喜欢

转载自blog.csdn.net/f1033774377/article/details/80570135