#8排序算法#

news2024/11/14 18:39:56

1.排序

1°概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起 来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记 录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍 在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据 的排序。

2°分类

2.直接插入排序

1°思路

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐 个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较找到插入位置即将array[i]插入原来位置上的元素顺序后移

记录已经有序的最后一个元素的下标 往前遍历  

记录已经有序的最后一个元素的下标+1的元素 也就是即将插入的元素

开始进行遍历的比较  即将插入的元素和前面已经有序的元素进行比较

前面的元素>即将插入的元素 就往后移动1位

前面的元素<=即将插入的元素 就跳出循环

最后跳出循环的end为已经有序的数组的最后一个元素(还没有往后挪1位的元素)

end+1处就是该元素放的位置

极端情况也是可以的

非极端情况:

 

极端情况:

end最后是-1

end+1就是0

也就是放在第一个元素的位置

2°实现

void InsertSort(int* a, int n)
{
	//下标遍历
	for (int i = 0; i < n - 1; i++)
	{
		//[0,end]有序 end+1的位置的值插入进去 让[0,end+1]有序
		int end = i;
		//先保存 防止覆盖
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				--end;
			}
			//可以插入的时候就break
			else
			{
				break;
			}
		}
		//插入的值比所有的都大或者都小
		//都大直接放到end+1的位置
		//都小的话 end最后出循环是-1 放到a[0]
		a[end + 1] = tmp;
	}
}

外循环遍历下标

记录有序数组最后一个元素的下标

内循环改变元素位置

如果插入元素小一些 大元素往后移动1位 同时end-- 可以保持end一直是最后一位 

如果插入元素大一些 直接跳出内循环 并且把插入元素放入end+1下标位置的元素

3°总结

(1)元素集合越接近有序,直接插入排序算法的时间效率越高

(2)时间复杂度:O(N^2)

(3)空间复杂度:O(1),它是一种稳定的排序算法

(4)稳定性:稳定

3.希尔排序

1°思路

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有 记录分成个组所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重 复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

先分组 再组内插入排序

最后gap==1的时候就是直接插入排序

写的时候只需要把1换成gap 其他逻辑和插入排序一样

2°实现

//希尔排序 插入排序的优化
//1.先进行预排序 接近有序
//2.直接插入排序
//预排序:分组排 间隔为gap是一组 多组预排序 gap由大到小
//gap越大:大的数可以越快的到后面
//小的数可以越快的到前面
//gap越大 预排完越不接近有序
//gap越小 越接近有序
//gap==1时就是直接插入排序
//时间复杂度:
//大循环:
//N/3/3/3…………/3/3=1
//log3的N
//小循环:
//gap很大时 下面预排的时间复杂度为O(N)
//gap很小时 数组已经很接近有序了 这时差不多也是O(N)
//时间复杂度O(N*log3 N)
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//gap = gap / 2;//可以保证最后一次为1
		gap = gap / 3 + 1;//保证最后一次为1
		//gap==1就是直接插入排序 把1换成gap
		//把间隔为gap的多组数据同时排
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

3°总结

(1) 希尔排序是对直接插入排序的优化

(2)当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的 了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

(3)希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^1.3— N^2)

(4)稳定性:不稳定

4.直接选择排序

1°思路

在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素

若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个) 元素交换

在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

2°实现

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void SelectSort(int* a, int n)
{
	int mini = 0;
	for (int i = 0; i < n; i++)
	{
		mini = i;
		//从下标i之后开始遍历
		for (int j = i + 1; j < n; j++)
		{
			//后面有数比下标i处小
			//就把下标给到最小值下标
			if (a[j] < a[mini])
			{
				mini = j;
			}
			Swap(&a[i], &a[mini]);
		}
	}
}

3°总结

(1)直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

(2)时间复杂度:O(N^2)

(3)空间复杂度:O(1)

(4)稳定性:不稳定

5.堆排序

1°思路 

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

我们排成升序 先要建大堆

然后把最后一个数和第一个数进行交换

最后一个数就是最大的数

然后不看最后一个数

重复上述过程

如何建大堆?

找出左右孩子中大的那一个

与父亲进行比较

如果父亲较小 就交换

如果父亲较大 就不动

如何找左右孩子中大的那一个?

先默认左孩子大 再进行判断 右孩子大就变成右孩子

通过结点关系来调整

注意:建堆的时候要倒着建

向下调整算法的前提是左右子树都是小堆或者大堆

倒着可以避免问题

2°实现

//调整成大堆
//时间复杂度O(N)
void AdjustDown(int* a, int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;//默认左孩子
	//孩子下标如果大于n 就超出了数组范围
	while (child < n)
	{
		//选出左右孩子大的那一个
		if (child + 1 < n && a[child + 1] > a[child])
		{
			//右孩子比左孩子大就变为右孩子
			child += 1;
		}
		//换成大于号 就变成大堆 小于号就小堆
		//上面if的小于号也要换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			//交换后孩子变为父亲 孩子重置
			parent = child;
			child = parent * 2 + 1;
		}
		//大的那个孩子比父亲都小 直接break
		else
		{
			break;
		}
	}
}

//堆排序
//建小堆
//前提:左右子树都是小堆(特殊情况)
//从根节点开始
//选出左右孩子中小的那一个 跟父亲比较
//如果比父亲小 就交换
//然后再继续往下调 调到叶子节点就终止
//整体时间复杂度O(N*logN)
void HeapSort(int* a, int n)
{
	//建堆
	//向下调整算法 左右子树不是小堆不能用
	//那怎么办? 倒着从最后一颗子树开始调
	//再分析倒着走 叶子不需要调 从倒数的非叶子子树开始调
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		//最后一个值的下标是n-1 算出他的父亲 
		//再往前遍历 先把后面调成小堆 
		AdjustDown(a, n, i);
	}
	//排成升序 建大堆
	//为什么?
	//如果是建小堆 最小数在堆顶 已经被选出来了
	//那么在剩下的数中再去选数(建堆) 但是剩下树结构都乱了
	//需要重新建堆才能选出下一个数 建堆的时间复杂度是O(N)
	//那么这样不是不可以 但是堆排序就没有效率优势了
	//把最大的换到最后 把他不看做堆里面
	//前n-1个数向下调整 选出次大的数 再跟倒数第二个位置交换
	//时间复杂度:N*logN
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

3°总结

(1)堆排序使用堆来选数,效率就高了很多。

(2)时间复杂度:O(N*logN)

(3)空间复杂度:O(1)

(4)稳定性:不稳定

6.冒泡排序

1°思路

外循环 循环n-1次

内循环 两两相邻的数进行比较

2°实现

//冒泡
//O(N)等差数列
//跟直接插入谁更好? 直接插入
void BubbleSort(int* a, int n)
{
	//默认有序
	int exchange = 0;
	//冒泡排序的次数
	for (int i = 0; i < n - 1; i++)
	{
		//一趟冒泡排序
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				//有交换
				exchange = 1;
			}
		}
		//说明已经有序
		if (exchange == 0)
		{
			break;
		}
	}
}

3°总结

(1)冒泡排序是一种非常容易理解的排序

(2)时间复杂度:O(N^2)

(3)空间复杂度:O(1)

(4)稳定性:稳定

7.快速排序

1°思路

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序 元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有 元素均小于基准值,右子序列中所有元素均大于基准值然后最左右子序列重复该过程,直到所 有元素都排列在相应位置上为止。

挖坑法

当数组已经有序了 使用快速排序的话 时间复杂度为O(N^2)

选择坑位一般选择第一个元素或者最后一个元素

当选择的数是最小或者最大时 就要进行很多次比较

因此三数取中是有必要的 最左边 最右边 中间

取中间大的数作为坑位

取第一个元素作为坑 从最右边找小 放到左边的坑位 然后形成新的坑位

再从最左边开始找大 放到右边的坑位 然后形成新的坑位

左右指针法

三数取中 找到key

right先开始找小 找到了小的 left开始找大 找到了大的就交换小的和大的

最后left==right的时候 把key放过来

前后指针法

keyi为第一个数的下标

prev为第一个数的下标

cur为第二个数的下标

cur往下遍历找小 找到小的 prev++ cur和prev下标对应元素交换

最后交换prev和keyi下标对应元素

key就是正确的位置

相当于把比key小的所有数找到了 并且放到了prev的前面

prev的位置就是key的位置

2°实现

三数取中

//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;//就是/2
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return a[left];
		}
		else
		{
			return a[right];
		}
	}
	else//a[left]>a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return a[left];
		}
		else
		{
			return a[right];
		}
	}
}

挖坑法

//快速排序
//挖坑法
//分治
//不有序:
//单趟:O(N)
//O(N*logN)
//快排什么情况下最坏 时间复杂度是多少?
//有序情况 O(N^2) 等差数列
int PartSort1(int* a, int left, int right)
{
	//有序情况很坏 这里进行三数取中
	//不让找到最大或者最小作为key
	int index = GetMidIndex(a, left, right);
	//交换 但还是原来的数做key 不会选到最小的
	Swap(&a[left], &a[index]);
	int begin = left;
	int end = right;
	int pivot = begin;
	int key = a[begin];
	while (begin < end)
	{
		//右边找小 放到左边 再判断一次 因为可能end--后导致错乱
		//防止越界
		while (begin < end && a[end] >= key)
		{
			//大了就往前走
			--end;
		}
		//小的放到左边的坑
		//自己形成新的坑位
		a[pivot] = a[end];
		pivot = end;
		//左边找大 放到右边
		while (begin < end && a[begin] <= key)
		{
			//小了就往后走
			++begin;
		}
		//大的放到右边的坑
		//自己形成新的坑位
		a[pivot] = a[begin];
		pivot = begin;
	}
	//相遇的时候就是坑的位置
	pivot = begin;
	a[pivot] = key;
	return pivot;
}

左右指针法

int PartSort2(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	int begin = left;
	int end = right;
	int keyi = begin;
	//先三数取中
	//left开始找大 right开始找小
	//找到交换 最后left==right的时候 把key放过来
	while (begin < end)
	{
		//找小
		while (begin < end && a[end] >= a[keyi])
		{
			--end;
		}
		//找大
		while (begin < end && a[begin] <= a[keyi])
		{
			++begin;
		}
		//交换
		Swap(&a[begin], &a[end]);
	}
	//把key拿到相遇的位置
	Swap(&a[begin], &a[keyi]);
	//返回key的下标
	return begin;
}

前后指针法

int PartSort3(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	//prev cur前后指针
	//cur找到小的就prev++ 交换
	//这样小的就到左边来了
	//最后prev的位置就是key的位置 交换
	//最后返回prev就是key的下标
	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			++prev;
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}

递归 快排

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//int keyIndex = PartSort1(a, left, right);
	//int keyIndex = PartSort2(a, left, right);
	int keyIndex = PartSort3(a, left, right);
	//[left,right]
	//[left,pivot-1] pivot [pivot+1,right]
	//左子区间有序 右子区间有序 就有序 分治
	//优化后面的递归 
	//只有10个数以下了
	//选用插入排序
	//第一个参数是数组 第二个参数是数据个数
	if (keyIndex - 1 - left > 10)
	{
		QuickSort(a, left, keyIndex - 1);
	}
	else
	{
		InsertSort(a + left, keyIndex - 1 - left + 1);
	}
	if (right - (keyIndex + 1) > 10)
	{
		QuickSort(a, keyIndex + 1, right);
	}
	else
	{
		InsertSort(a + keyIndex + 1,right - (keyIndex + 1) + 1);
	}
}

3°总结

(1)快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

(2)时间复杂度:O(N*logN)

(3)空间复杂度:O(logN)

(4)稳定性:不稳定

8.归并排序

1°思路

找到mid 不停的拆分 当只有一个数的时候已经有序了

然后再不停的合并 看成两个有序数组的合并

2°实现

//分成左半区间和右半区间
//两区间如果都有序
//看成两个有序数组的合并
//取小的 放到新的临时数组 再拷贝回原数组
//那么归并之前 左右子区间没有序 怎么办?
//分治递归 再分

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
		return;
	int mid = (left + right) >> 1;
	//假设[left,mid] [mid+1,right]有序 就可以归并
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	//两个有序数组的合并
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int index = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	//不知道谁先到end 直接写两个循环 把另一个数组放到后面
	//这两个循环只会进1个
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	//拷贝回去
	for (int i = left; i <= right; i++)
	{
		a[i] = tmp[i];
	}
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

3°总结

(1)归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问 题。

(2)时间复杂度:O(N*logN)  

(3)空间复杂度:O(N)

(4)稳定性:稳定

9.非递归快排和归并

快排

需要借助栈

选一个key先排好

开始​​​把左右的子区间(如果无序)放到栈里面

再取出来再选key 重复走

最后只有一个数就有序了

//非递归快排
//递归缺陷:栈帧深度太深 栈空间不够用 可能会溢出
//递归改非递归:1.直接改循环 2.借助数据结构栈模拟递归过程(复杂)
void QuickSortNonR(int* a, int n)
{
	ST st;
	StackInit(&st);
	StackPush(&st, n - 1);
	StackPush(&st, 0);
	//取左右下标
	while (!StackEmpty(&st))
	{
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		//单趟排序
		int keyIndex = PartSort3(a, left, right);
		//[left,keyIndex-1] keyIndex [keyIndex+1,right]
		//只有一个值就有序了
		if (keyIndex + 1 < right)
		{
			StackPush(&st, right);
			StackPush(&st, keyIndex + 1);
		}

		if (left < keyIndex - 1)
		{
			StackPush(&st, keyIndex - 1);
			StackPush(&st, left);
		}
	}
}

归并

两两一组 一开始一组只有1个数 后面每合并一次 一组的数据个数就*2

这里要注意右边区间可能会越界 所以需要修正

//非递归归并
//时间复杂度O(N*logN)
void MergeSortNonR(int* a, int n)
{
	int gap = 1;//每一组数据的个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//两组进行归并 最后i+=2*gap控制次数
			//[i,i+gap-1] [i+gap,i+2*gap-1]
			//两个有序数组的合并
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;
			//归并过程中右半区间可能就不存在
			if (begin2 >= n)
				break;
			//归并过程中右半区间算多了
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			//不知道谁先到end 直接写两个循环 把另一个数组放到后面
			//这两个循环只会进1个
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			//拷贝回去
			for (int j = 0; j <= end2; j++)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;
	}
	free(tmp);
}

10.计数排序

统计每个数出现的次数

开辟先把所有元素初始化为0

然后开始计数

用次数进行排序

//非比较排序
//时间复杂度:O(N+range) 说明他适用于集中一组整形数据排序
//空间复杂度:O(range)
void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count, 0, sizeof(int) * range);
	//统计次数
	for (int i = 0; i < n; i++)
	{
		//相对映射
		count[a[i]-min]++;
	}
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			//加回去
			a[j++] = i + min;
		}
	}
}

11.测试

//打印数组
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
#define _CRT_SECURE_NO_WARNINGS 1

#include "Sort.h"

void TestInsertSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	InsertSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestShellSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	ShellSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestHeapSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	HeapSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestSelectSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	SelectSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestBubbleSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	BubbleSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestQuickSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	QuickSort(a, 0, sizeof(a) / sizeof(int) - 1);
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestMergeSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	MergeSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestQuickSortNonR()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	QuickSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestMergeSortNonR()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	MergeSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestCountSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	CountSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
	}
	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();
	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();
	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();
	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();
	int begin5 = clock();
	QuickSort(a5, 0, N - 1);
	int end5 = clock();
	int begin6 = clock();
	MergeSort(a6, N);
	int end6 = clock();
	int begin7 = clock();
	BubbleSort(a7, N);
	int end7 = clock();
	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("BubbleSort:%d\n", end7 - begin7);
	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}

int main()
{
	TestInsertSort();
	TestShellSort();
	TestHeapSort();
	TestSelectSort();
	TestBubbleSort();
	TestQuickSort();
    TestMergeSort();
	TestQuickSortNonR();
	TestMergeSortNonR();
	TestCountSort();
	TestOP();
	return 0;
}

12.总结与完整代码

Sort.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <Windows.h>

//打印数组
void PrintArray(int* a, int n);

//插入排序
void InsertSort(int* a, int n);

//希尔排序 插入排序的优化
void ShellSort(int* a, int n);

//交换
void Swap(int* p1, int* p2);

//调整成小堆
void AdjustDown(int* a, int n, int root);

//堆排序(想象成完全二叉树)(实际是数组)
//大堆:树中所有的父亲都大于等于孩子 根是最大的
//小堆:树中所有的母亲都小于等于孩子 根是最小的
void HeapSort(int* a, int n);

//直接选择排序
void SelectSort(int* a, int n);

//冒泡排序
void BubbleSort(int* a, int n);

//快速排序
void QuickSort(int* a, int left, int right);

//三数取中
int GetMidIndex(int* a, int left, int right);

//挖坑法
int PartSort1(int* a, int left, int right);

//左右指针法
int PartSort2(int* a, int left, int right);

//前后指针法
int PartSort3(int* a, int left, int right);

//归并排序
void MergeSort(int* a, int n);

//归并子函数
void _MergeSort(int* a, int left, int right, int* tmp);

//栈的构建
typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;//栈顶的位置
	int capacity;
}ST;

//初始化
void StackInit(ST* ps);

//销毁
void StackDestroy(ST* ps);

//栈底插入
void StackPush(ST* ps, STDataType x);

//栈底删除
void StackPop(ST* ps);

//栈顶数据
STDataType StackTop(ST* ps);

//栈的大小
int StackSize(ST* ps);

//栈是否为空
bool StackEmpty(ST* ps);

//非递归快排
void QuickSortNonR(int* a, int n);

//非递归归并
void MergeSortNonR(int* a, int n);

//计数排序
void CountSort(int* a, int n);

Stack.c

#define _CRT_SECURE_NO_WARNINGS 1

#define _CRT_SECURE_NO_WARNINGS 1

#include "Sort.h"

//初始化
void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	//初始化 top给的是0 意味着top指向栈顶数据的下一个
	//先放数据 再加1
	//初始化 top给的是-1 意味着top指向栈顶数据
	//先加1 再放数据
	ps->capacity = 0;
}

//销毁
void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

//栈定插入
void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

//栈顶删除
void StackPop(ST* ps)
{
	assert(ps);
	//警告删完了还删
	assert(!StackEmpty(ps));
	ps->top--;
}

//栈顶数据
STDataType StackTop(ST* ps)
{
	assert(ps);
	//不为空再返回栈顶
	assert(!StackEmpty(ps));
	//存完之后top会自加 因此top-1处是栈顶
	return ps->a[ps->top - 1];
}

//栈的大小
int StackSize(ST* ps)
{
	assert(ps);
	//top的大小就是size
	return ps->top;
}

//栈是否为空
bool StackEmpty(ST* ps)
{
	assert(ps);
	//top==0为真 返回1 栈是空的
	//top!=0为假 返回0 栈不是空的
	return ps->top == 0;
}

Sort.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Sort.h"

//打印数组
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

//插入排序
//时间复杂度O(n^2) 
//最坏 逆序 每次都要走完整个循环
//1+2+3+…………+n-1=n*(n-1)/2 O(n^2)
//最好 顺序有序 1+1+1+1…………+1+1=n-1 O(n)
void InsertSort(int* a, int n)
{
	//下标遍历
	for (int i = 0; i < n - 1; i++)
	{
		//[0,end]有序 end+1的位置的值插入进去 让[0,end+1]有序
		int end = i;
		//先保存 防止覆盖
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				--end;
			}
			//可以插入的时候就break
			else
			{
				break;
			}
		}
		//插入的值比所有的都大或者都小
		//都大直接放到end+1的位置
		//都小的话 end最后出循环是-1 放到a[0]
		a[end + 1] = tmp;
	}
}

//希尔排序 插入排序的优化
//1.先进行预排序 接近有序
//2.直接插入排序
//预排序:分组排 间隔为gap是一组 多组预排序 gap由大到小
//gap越大:大的数可以越快的到后面
//小的数可以越快的到前面
//gap越大 预排完越不接近有序
//gap越小 越接近有序
//gap==1时就是直接插入排序
//时间复杂度:
//大循环:
//N/3/3/3…………/3/3=1
//log3的N
//小循环:
//gap很大时 下面预排的时间复杂度为O(N)
//gap很小时 数组已经很接近有序了 这时差不多也是O(N)
//时间复杂度O(N*log3 N)
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//gap = gap / 2;//可以保证最后一次为1
		gap = gap / 3 + 1;//保证最后一次为1
		//gap==1就是直接插入排序 把1换成gap
		//把间隔为gap的多组数据同时排
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//调整成大堆
//时间复杂度O(N)
void AdjustDown(int* a, int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;//默认左孩子
	//孩子下标如果大于n 就超出了数组范围
	while (child < n)
	{
		//选出左右孩子大的那一个
		if (child + 1 < n && a[child + 1] > a[child])
		{
			//右孩子比左孩子大就变为右孩子
			child += 1;
		}
		//换成大于号 就变成大堆 小于号就小堆
		//上面if的小于号也要换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			//交换后孩子变为父亲 孩子重置
			parent = child;
			child = parent * 2 + 1;
		}
		//大的那个孩子比父亲都小 直接break
		else
		{
			break;
		}
	}
}

//堆排序
//建小堆
//前提:左右子树都是小堆(特殊情况)
//从根节点开始
//选出左右孩子中小的那一个 跟父亲比较
//如果比父亲小 就交换
//然后再继续往下调 调到叶子节点就终止
//整体时间复杂度O(N*logN)
void HeapSort(int* a, int n)
{
	//建堆
	//向下调整算法 左右子树不是小堆不能用
	//那怎么办? 倒着从最后一颗子树开始调
	//再分析倒着走 叶子不需要调 从倒数的非叶子子树开始调
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		//最后一个值的下标是n-1 算出他的父亲 
		//再往前遍历 先把后面调成小堆 
		AdjustDown(a, n, i);
	}
	//排成升序 建大堆
	//为什么?
	//如果是建小堆 最小数在堆顶 已经被选出来了
	//那么在剩下的数中再去选数(建堆) 但是剩下树结构都乱了
	//需要重新建堆才能选出下一个数 建堆的时间复杂度是O(N)
	//那么这样不是不可以 但是堆排序就没有效率优势了
	//把最大的换到最后 把他不看做堆里面
	//前n-1个数向下调整 选出次大的数 再跟倒数第二个位置交换
	//时间复杂度:N*logN
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

//时间复杂度O(N^2)
//选择排序
void SelectSort(int* a, int n)
{
	//int begin = 0;
	//int end = n - 1;
	//while (begin < end)
	//{
	//	int mini = begin;
	//	int maxi = begin;
	//	for (int i = begin; i <= end; i++)
	//	{
	//		if (a[i] < a[mini])
	//		{
	//			mini = i;
	//		}
	//		if (a[i] > a[maxi])
	//		{
	//			maxi = i;
	//		}
	//	}
	//	Swap(&a[begin], &a[mini]);
	//	//如果begin和maxi重叠 
	//	//需要修正一下maxi的位置
	//	if (begin == maxi)
	//	{
	//		maxi = mini;
	//	}
	//	Swap(&a[end], &a[maxi]);
	//	++begin;
	//	--end;
	//}
	int mini = 0;
	for (int i = 0; i < n; i++)
	{
		mini = i;
		//从下标i之后开始遍历
		for (int j = i + 1; j < n; j++)
		{
			//后面有数比下标i处小
			//就把下标给到最小值下标
			if (a[j] < a[mini])
			{
				mini = j;
			}
			Swap(&a[i], &a[mini]);
		}
	}
}

//冒泡
//O(N)等差数列
//跟直接插入谁更好? 直接插入
void BubbleSort(int* a, int n)
{
	//默认有序
	int exchange = 0;
	//冒泡排序的次数
	for (int i = 0; i < n - 1; i++)
	{
		//一趟冒泡排序
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				//有交换
				exchange = 1;
			}
		}
		//说明已经有序
		if (exchange == 0)
		{
			break;
		}
	}
}

//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;//就是/2
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return a[left];
		}
		else
		{
			return a[right];
		}
	}
	else//a[left]>a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return a[left];
		}
		else
		{
			return a[right];
		}
	}
}


//快速排序
//挖坑法
//分治
//不有序:
//单趟:O(N)
//O(N*logN)
//快排什么情况下最坏 时间复杂度是多少?
//有序情况 O(N^2) 等差数列
int PartSort1(int* a, int left, int right)
{
	//有序情况很坏 这里进行三数取中
	//不让找到最大或者最小作为key
	int index = GetMidIndex(a, left, right);
	//交换 但还是原来的数做key 不会选到最小的
	Swap(&a[left], &a[index]);
	int begin = left;
	int end = right;
	int pivot = begin;
	int key = a[begin];
	while (begin < end)
	{
		//右边找小 放到左边 再判断一次 因为可能end--后导致错乱
		//防止越界
		while (begin < end && a[end] >= key)
		{
			//大了就往前走
			--end;
		}
		//小的放到左边的坑
		//自己形成新的坑位
		a[pivot] = a[end];
		pivot = end;
		//左边找大 放到右边
		while (begin < end && a[begin] <= key)
		{
			//小了就往后走
			++begin;
		}
		//大的放到右边的坑
		//自己形成新的坑位
		a[pivot] = a[begin];
		pivot = begin;
	}
	//相遇的时候就是坑的位置
	pivot = begin;
	a[pivot] = key;
	return pivot;
}

int PartSort2(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	int begin = left;
	int end = right;
	int keyi = begin;
	//先三数取中
	//left开始找大 right开始找小
	//找到交换 最后left==right的时候 把key放过来
	while (begin < end)
	{
		//找小
		while (begin < end && a[end] >= a[keyi])
		{
			--end;
		}
		//找大
		while (begin < end && a[begin] <= a[keyi])
		{
			++begin;
		}
		//交换
		Swap(&a[begin], &a[end]);
	}
	//把key拿到相遇的位置
	Swap(&a[begin], &a[keyi]);
	//返回key的下标
	return begin;
}

int PartSort3(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	//prev cur前后指针
	//cur找到小的就prev++ 交换
	//这样小的就到左边来了
	//最后prev的位置就是key的位置 交换
	//最后返回prev就是key的下标
	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			++prev;
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}


void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//int keyIndex = PartSort1(a, left, right);
	//int keyIndex = PartSort2(a, left, right);
	int keyIndex = PartSort3(a, left, right);
	//[left,right]
	//[left,pivot-1] pivot [pivot+1,right]
	//左子区间有序 右子区间有序 就有序 分治
	//优化后面的递归 
	//只有10个数以下了
	//选用插入排序
	//第一个参数是数组 第二个参数是数据个数
	if (keyIndex - 1 - left > 10)
	{
		QuickSort(a, left, keyIndex - 1);
	}
	else
	{
		InsertSort(a + left, keyIndex - 1 - left + 1);
	}
	if (right - (keyIndex + 1) > 10)
	{
		QuickSort(a, keyIndex + 1, right);
	}
	else
	{
		InsertSort(a + keyIndex + 1,right - (keyIndex + 1) + 1);
	}
}

//分成左半区间和右半区间
//两区间如果都有序
//看成两个有序数组的合并
//取小的 放到新的临时数组 再拷贝回原数组
//那么归并之前 左右子区间没有序 怎么办?
//分治递归 再分

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
		return;
	int mid = (left + right) >> 1;
	//假设[left,mid] [mid+1,right]有序 就可以归并
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	//两个有序数组的合并
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int index = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	//不知道谁先到end 直接写两个循环 把另一个数组放到后面
	//这两个循环只会进1个
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	//拷贝回去
	for (int i = left; i <= right; i++)
	{
		a[i] = tmp[i];
	}
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}


//非递归快排
//递归缺陷:栈帧深度太深 栈空间不够用 可能会溢出
//递归改非递归:1.直接改循环 2.借助数据结构栈模拟递归过程(复杂)
void QuickSortNonR(int* a, int n)
{
	ST st;
	StackInit(&st);
	StackPush(&st, n - 1);
	StackPush(&st, 0);
	//取左右下标
	while (!StackEmpty(&st))
	{
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		//单趟排序
		int keyIndex = PartSort3(a, left, right);
		//[left,keyIndex-1] keyIndex [keyIndex+1,right]
		//只有一个值就有序了
		if (keyIndex + 1 < right)
		{
			StackPush(&st, right);
			StackPush(&st, keyIndex + 1);
		}

		if (left < keyIndex - 1)
		{
			StackPush(&st, keyIndex - 1);
			StackPush(&st, left);
		}
	}
}

//非递归归并
//时间复杂度O(N*logN)
void MergeSortNonR(int* a, int n)
{
	int gap = 1;//每一组数据的个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//两组进行归并 最后i+=2*gap控制次数
			//[i,i+gap-1] [i+gap,i+2*gap-1]
			//两个有序数组的合并
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;
			//归并过程中右半区间可能就不存在
			if (begin2 >= n)
				break;
			//归并过程中右半区间算多了
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			//不知道谁先到end 直接写两个循环 把另一个数组放到后面
			//这两个循环只会进1个
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			//拷贝回去
			for (int j = 0; j <= end2; j++)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;
	}
	free(tmp);
}

//归并排序 也叫外排序
//假设10G的数据放到硬盘的文件中 要排序 如何排?
//10G的文件 切分成10个1G的文件
//并且让这10个1G的文件有序
//依次读文件 每次读1G到内存中放到一个数组
//用快速排序对其排序
//再写到一个文件 再继续读下一个1G的数据

//基数排序 桶排序
//123 45 12 9 88 43
//依次分别取他们的个位 十位 百位……排序
//实际中这个排序没啥意义

//计数排序
//4 4 6 8 9 3 3 0
//一个数组开10个
//1.统计每个数出现的次数
//初始化数组为0 下标对应的地方++计数
//2.使用次数就可以排序了
//0 0 3 3 4 4 6 8 9
//如果100 101 102 101 109 105  开到109
//前面0-99会浪费
//相对映射位置:num-min


//非比较排序
//时间复杂度:O(N+range) 说明他适用于集中一组整形数据排序
//空间复杂度:O(range)
void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count, 0, sizeof(int) * range);
	//统计次数
	for (int i = 0; i < n; i++)
	{
		//相对映射
		count[a[i]-min]++;
	}
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			//加回去
			a[j++] = i + min;
		}
	}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Sort.h"

void TestInsertSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	InsertSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestShellSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	ShellSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestHeapSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	HeapSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestSelectSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	SelectSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestBubbleSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	BubbleSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestQuickSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	QuickSort(a, 0, sizeof(a) / sizeof(int) - 1);
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestMergeSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	MergeSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestQuickSortNonR()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	QuickSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestMergeSortNonR()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	MergeSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestCountSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	CountSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
	}
	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();
	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();
	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();
	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();
	int begin5 = clock();
	QuickSort(a5, 0, N - 1);
	int end5 = clock();
	int begin6 = clock();
	MergeSort(a6, N);
	int end6 = clock();
	int begin7 = clock();
	BubbleSort(a7, N);
	int end7 = clock();
	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("BubbleSort:%d\n", end7 - begin7);
	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}

int main()
{
	TestInsertSort();
	TestShellSort();
	TestHeapSort();
	TestSelectSort();
	TestBubbleSort();
	TestQuickSort();
    TestMergeSort();
	TestQuickSortNonR();
	TestMergeSortNonR();
	TestCountSort();
	TestOP();
	return 0;
}

#8排序算法#完

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/351277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AWS攻略——使用ACL限制访问

文章目录确定出口IP修改ACL修改主网络ACL修改入站规则修改子网ACL创建子网ACL新增入站规则新增出站规则关联子网假如我们希望限制只有公司内部的IP可以SSH登录到EC2&#xff0c;则可以考虑使用ACL来实现。 我们延续使用《AWS攻略——创建VPC》的案例&#xff0c;在它的基础上做…

Spring 系列 - AOP

Spring 系列 - AOP Spring 框架从使用到现在已经有相当的长的一段时间了&#xff0c;但总是在使用的时候&#xff0c;感觉一直停留在表面&#xff0c;对框架的底层了解的并不多&#xff0c;最近一段时间&#xff0c;打算好好折腾一下 Spring 的底层&#xff0c;想对 Spring 有…

智能家居项目(二)之工厂模式的实现

目录 一、举例说明&#xff1a;把下面的代码转换成工厂模式的方式来实现 二、用工厂模式的方式来实现 1、创建三个函数的文件 2、创建一个 Animal.h文件&#xff0c;把上述三个函数名都放在这个文件中 3、创建一个main主函数文件 4、用工厂模式创建的文件浏览 一、举例说…

用主动游泳的三维水母模型量化美杜莎的(medusan)机械空间的性能(三)(2017)

文章目录用主动游泳的三维水母模型量化美杜莎的&#xff08;medusan&#xff09;机械空间的性能&#xff08;三&#xff09;(2017)原文链接&#xff1a;https://doi.org/10.1017/jfm.2017.34. 讨论小结用主动游泳的三维水母模型量化美杜莎的&#xff08;medusan&#xff09;机械…

iptables防火墙详解

目录iptables防火墙iptables简介iptables表filter(过滤表)nat(网络地址转换表)mangle(修改表)raw(原始表)security 表处理目标REJECT(拒绝)DNAT(目的网络地址转换)SNAT(源网络地址转换)MASQUERADE(伪装)LOGREDIRECT报文处理流程规则表之间的优先顺序报文规则匹配管理防火墙规则…

单调栈---神奇的栈

我们平时用的栈多&#xff0c;但是我们一般用的是什么呢&#xff1f;用来做有效的括号匹配&#xff1f;还是用来记录我们的二叉树的节点&#xff1f; 通过对栈的理解&#xff0c;我们学习一个新的概念–单调栈。所谓单调栈&#xff0c;就是单调递增或者单调递减的栈。 那么单…

第四届宁波网安市赛初赛

由于赛后官方不给wp&#xff08;绿盟一向如此&#xff09;&#xff0c;交流群也没有得到其他题解&#xff0c;赛后就根据自己的wp来写了&#xff0c;wp由队友及我共同完成。比赛共解答10题&#xff0c;Web 5道&#xff0c;Misc 2道&#xff0c;Crypto 3道&#xff0c;wp只有一部…

深度学习算法面试常问问题(一)

博主秋招遇到的面试问题以及整理其他面经相关问题&#xff0c;无偿分享~ 项目叙述&#xff1a; 算法需求及应用场景算法的调研和初步方案的制定数据的准备&#xff08;包括数据标注和数据增强&#xff09;算法的介绍&#xff08;包括输入和输出&#xff0c;loss、backbone、训…

做外贸,你不能不懂的外贸流程知识

报关是履行海关进出境手续的必要环节之一&#xff0c;涉及两大类:进出境运输工具、物品和货物。由于性质不同&#xff0c;报关手续也有些不同。今天我就为大家详细介绍一下进出口报关的流程&#xff0c;包括出口货物报关的流程&#xff0c;随报关单提交的运费和商业单据&#x…

Spark中cache、persist、checkpoint三者的比较

原文链接&#xff1a;https://blog.csdn.net/AnameJL/article/details/124362219&#xff0c;再次主要是为了方便查找&#xff0c;把原文章复制一遍 目录 1. 三者的使用 1.1 cache的讲解与使用 1.2 persist的讲解与使用 1.3checkpoint 的讲解与使用 2. 三者的比较 2.1 优…

基于Web的智慧能源可视化管理系统

自工业革命开始&#xff0c;全球能源消耗持续增长&#xff0c;由碳循环体系破坏引发的全球变暖、海平面上升等问题严重影响着人类的可持续发展。得益于数字孪生、物联网、5G、AI识别等技术的不断成熟&#xff0c;以“大数据能源监控、精细化能源管理”为核心的智慧能源解决方案…

【电子学会】2022年12月图形化三级 -- 绘制雷达

绘制雷达 1. 准备工作 &#xff08;1&#xff09;绘制黑色背景&#xff1b; &#xff08;2&#xff09;删除默认角色小猫&#xff0c;添加角色Pencil。 2. 功能实现 &#xff08;1&#xff09;画笔的颜色设为黄色&#xff0c;Pencil的中心点设在笔尖位置&#xff0c;画笔的…

在Java 中 利用Milo通信库,实现OPCUA客户端,并生成证书

程序结构&#xff1a; 配置文件resources&#xff1a; opcua.properties 西门子PLC端口号为4840&#xff0c;kepserver为49320 #opcua服务端配置参数 #opcua.server.endpoint.urlopc.tcp://192.168.2.102:49320 opcua.server.endpoint.urlopc.tcp://192.168.2.11:4840 opcu…

2023年私募股权基金研究报告

第一章 概况 PE是私募&#xff0c;也即私募投资基金&#xff0c;是指以非公开发行方式向合格投资者募集的&#xff0c;投资于股票、股权、债券、期货、期权、基金份额及投资合同约定的其他投资标的&#xff08;如艺术品、红酒等&#xff09;的投资基金&#xff0c;简称私募基金…

【PyTorch学习3】《PyTorch深度学习实践》——反向传播(Back Propagation)

目录一、Tensor1.定义2.Tensor常见形式3.torch.tensor和torch.Tensor4.Tensor.grad二、反向传播一、Tensor 1.定义 张量的定义是一个可用来表示在一些矢量、标量和其他张量之间的线性关系的多线性函数。 在PyTorch上有这样一句话&#xff0c;A torch.Tensor is a multi…

TeeChart for .NET 2023.2.13 Crack

TeeChart NET Pro Edition 是一个基于 Nuget 的图表控件&#xff0c;旨在为您的 NET 应用程序提供即时图表、地图和仪表功能。TeeChart 拥有数十种图表类型、统计函数和开发人员工具&#xff0c;是当今开发人员可用的最全面的图表库之一。易于使用设计时编辑器和直观的编程结构…

蒙特卡洛计算圆周率

使用MC计算圆周率的小例子&#xff0c;使用python的numpy&#xff0c;matplotlib库import numpy as npimport matplotlib.pyplot as pltdef mc_calculate_pi(t):np.random.seed(t)rand_num np.random.rand(t)rand_num2 np.random.rand(t)l1 rand_num-0.5l2 rand_num2-0.5l0…

记录-配置unity多人游戏服务器中的踩坑经历

最近沉迷于gpt这个“魔法海螺”无法自拔&#xff0c;总之这是题外话了&#xff0c;Let.s go 文章目录前言一、IIS的HTTPS站点设置二、VS的远程部署设置三、运行.NET CORE的IIS站点——注意项四、SQL Server使用sa账号不能登录的问题解决五、SVN忽略so文件的提交&#xff0c;导致…

深入浅出带你学习IIS中间件常见漏洞

前言 在渗透过程中我们经常会思考如何去进行渗透&#xff0c;假如给了我们一个有很多中间件的网站我们要如何进行渗透呢&#xff1f;于是本人准备写一些包含常见中间件漏洞攻击的总结&#xff0c;希望可以帮到大家在面临不同渗透环境时会有渗透思路&#xff0c;本篇文章就先给…

开源单点登录MaxKey和JumpServer 堡垒机单点登录集成指南

1. MaxKey介绍 MaxKey社区专注于身份安全管理(IM)、单点登录(SSO)和云身份认证(IDaas)领域&#xff0c;将为客户提供企业级的身份管理和认证&#xff0c;提供全面的4A安全管理&#xff08;指Account&#xff0c;Authentication&#xff0c;Authorization和Audit&#xff09;。…