常见排序算法

news2025/1/20 1:49:39

目录

一、插入排序

1、直接插入排序

2、希尔排序(缩小增量插入排序)

二、选择排序

三、堆排序

四、冒泡排序

五、快速排序(递归)

1、交换法

2、挖坑法

3、前后指针法(推荐)

4、快排再优化

六、快速排序(非递归)

七、归并排序

1、递归版

2、非递归版(改循环)

八、计数排序


一、插入排序

1、直接插入排序

时间复杂度

最坏:O(N^2)

最好:O(N)

思路:我们打牌时整牌的过程其实用的就是插入排序的思想。

如:3 9 5 4 排升序

除了第一个数3外,剩下的数都可以看成依次插入的数。

当插入9时,end指针指向3,3<9,9就放在end的下一个位置。

接着插入5,end指向9,9>5,end往前走指向3,3<5, 5就放在end(3)的下一个位置。

再继续插入4,end指向5,5>4,end往前走指向9,

9>4,end往前走指向3,3<4,4就放在end(3)的下一个位置。

void InsertSort(int* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		int tmp = a[i];
		int end = i - 1;
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

2、希尔排序(缩小增量插入排序)

时间复杂度:O(N^1.3)大致可理解为nlogn
思路:

 1) 预排序(使数组接近有序)

 2)直接插入排序

分组进行插入排序,间隔为gap的分一组:

假设gap=3

void shellSort(int*a,int n)
{
	int gap = 3;
	for (int i = 0; i < n - gap; i++)
	{
		int end = i;
		int tmp = a[end + gap];
		while (end>=0)
		{
			if (tmp < a[end])
			{
				a[end + gap] = a[end];
				a[end] = tmp;
				end -= gap;
			}
			else
			{
				break;
			}
		}
		a[end + gap] = tmp;
	}
}

gap为多少合适呢?

gap越大,跳得越快,越无序

gap越小,跳得越慢,越有序

可以这样:

gap=ngap/=2

也可以gap/=3+1(最后gap一定会变为1)

参考代码:

void shellSort(int*a,int n)
{

	int gap = n;
	while (gap>1)
	{
		gap/= 2;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					a[end] = tmp;
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

二、选择排序

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

思路:选出最大最小的数放两端

//选择排序
void SelectSort(int*a,int n)
{
	int left = 0, right = n - 1;
	while (left<right)
	{
		int mini = left, maxi = left;

		for (int i = left+1; i <= right; i++)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			if (a[i] < a[mini])
			{
				mini = i;
			}
		}
		swap(&a[left], &a[mini]);
		if (left==maxi) 
		{
			maxi = mini;
		}
		swap(&a[right], &a[maxi]);
		left++;
		right--;
	}
}

三、堆排序

参考之前的博文:https://blog.csdn.net/m0_73065213/article/details/129733076?spm=1001.2014.3001.5501

四、冒泡排序

时间复杂度:

最坏:O(N^2)

最好O(N)

前面有写博客专门讲过,详细讲解参考之前的博文。

void BubbleSort(int* a, int n)
{
	int j = 0, i = 0;
	for (i = 0; i < n-1; i++)//n个数只要调n-1躺
	{
		for (j = 0; j < n - 1-i; j++)//一趟比几次
		{
			if (a[j] > a[j + 1])
			{
				swap(&a[j], &a[j + 1]);
			}
		}
	}
}

五、快速排序(递归)

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

基本思想:任取待排序元素中的某元素作为基准值,按照该排序码将待排序列分割成两个子序列,左子序列中所有元素均小于等于基准值,右子序列所有元素均大于等于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

1、交换法

步骤:

1、确定一个基准值key

2、调位置

        左右两个指针,左找大,右找小,找到后交换位置。

        让左边小于等于基准值,让右边大于等于基准值。

3、递归调左右子序列

void QuickSort(int*a,int left,int right)
{
	int begin = left, end = right;
	//递归返回条件
	//left是可能大于right
	if (left >= right)return;
	
	int keyi = left;
	while (left < right)
	{
		//坑1:内层两个循环一定要判断left<right,不然right一直--,可能会越界
		//坑2:等于的时候也要++,不然当两边都有a[keyi]时,会死循环
        //坑3:左边做keyi,右边先走
		while (left<right && a[right] >= a[keyi])
			right--;
		while (left<right && a[left] <= a[keyi])
			left++;
		Swap(&a[right], &a[left]);
	}
	Swap(&a[keyi], &a[left]);//相遇位置一定小于等于基准值,后面会证明
	keyi = left;//keyi的位置就定了
	//递归左右区间
	//[begin,keyi-1] keyi [keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

按照上面的写法,keyi取left的话,若序列原本就有序,效率就会变成N^2。

因此,为了优化这个问题,下面介绍两种方法:

方法一:随机选keyi

void QuickSort(int*a,int left,int right)
{
	//递归返回条件
	//left是可能大于right
	if (left >= right)return;

	int begin = left, end = right;
	int randi = left+(rand() % (right - left));
	Swap(&a[left], &a[randi]);
	int keyi = left;
	while (left < right)
	{
		//坑1:一定要判断left<right,不然right一直--,可能会越界
		//坑2:等于的时候也要++,不然当两边都有a[keyi]时,会死循环
		while (left<right && a[right] >= a[keyi])
			right--;
		while (left<right && a[left] <= a[keyi])
			left++;
		Swap(&a[right], &a[left]);
	}
	Swap(&a[keyi], &a[left]);//相遇位置一定小于等于基准值
	keyi = left;//keyi的位置就定了
	//递归左右区间
	//[begin,keyi-1] keyi [keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

方法二:三数取中

用前中后三个数中不是最大也不是最小的数做keyi。

	//三数取中
	int midi = GetMidNumi(a, left, right);
	Swap(&a[left], &a[midi]);
	int keyi = left;
int GetMidNumi(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > right)
		{
			return mid;
		}
		else if (a[right] < a[left])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}

为什么相遇位置一定比keyi小?

☆左边做keyi,右边先走时,相遇位置一定比keyi小

☆右边做keyi,左边先走时,相遇位置一定比keyi大

相遇可能的几种情况:

1、right找到小,left还没找到就相遇了,所以相遇位置一定比keyi小。

2、right找小,没找到,就跟left相遇了,相遇的位置是上一轮交换的数字或者left还在原地,也是比keyi小的或者相等。

2、挖坑法

这种方法是把left位置的值用key保存,然后right找到大的值放left位置,再将left找到的小的值放right位置。

下面是单趟的代码:

	int key = a[left];
	int hole = left;
	while (left<right)
	{
		while (left<right && a[right]>key)
			right--;
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] < key)
			left++;
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;

3、前后指针法(推荐)

void QuickSort3(int* a, int left, int right)
{
	if (left >= right)return;
	int begin = left, end = right;
	int midi = GetMidNumi(a, left, right);
	Swap(&a[left], &a[midi]);
	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)//如果a[cur]>key,prev就不会++
			Swap(&a[cur], &a[prev]);
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	QuickSort3(a, begin, keyi - 1);
	QuickSort3(a, keyi + 1, end);
}

4、快排再优化

数据量很小时,用快排其实是很麻烦的,因此,当快排递归到最后几层时,递归的区间很小,我们可以选择用插入排序,这样快排的效率就大大优化了。

	if ((right - left + 1) > 10)
	{
		QuickSort3(a, left, right);
	}
	else
	{
		InsertSort(a + left, right - left + 1);
	}

此外,当数据重复较多时,快排效率也会大大降低,此时可以用三路划分的方法进行优化

六、快速排序(非递归)

前面我们都是用递归将区间不断的分小,

非递归的快排是用栈模拟递归将区间不断划分的过程。

如a[5]={2,1,4,3,5}

数组下标范围是0-4,我们先让4入栈,再让0入栈(因为栈的先进后出原则)

第一轮while:

取出栈中区间0-4,进行一轮排序后,得到>keyi的区间和<keyi的区间,如果这两个区间存在,就将两个区间存入栈。

对于此例,此时keyi为3,keyi+1=4=end所以大于keyi的区间不存在;

keyi-1=2>0,区间存在,让0-2入栈。

第二轮while:

取出栈中区间0-2,进行一轮排序后,得到>keyi的区间和<keyi的区间,如果这两个区间存在,就将两个区间存入栈。

keyi=1,keyi+1=end,keyi-1=begin,所以区间不存在,不入数据

此时,栈为空,循环结束。

void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);
	while (!isEmpty(&st))
	{
		int begin = StackTop(&st);
		StackPop(&st);
		int end = StackTop(&st);
		StackPop(&st);
		int keyi = PartSort3(a, begin, end);
		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}
		if (keyi - 1 > begin)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}
	Destroy(&st);
}

七、归并排序

1、递归版

时间复杂度:N*log(N)

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (end <= begin)
		return;
	int mid = (begin + end) / 2;
	//[begin,mid][mid+1,end]递归
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
	//[begin,mid][mid+1,end]归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//把剩下的全放过去
	//注意:这里是闭区间!!!
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2<=end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a+begin, tmp+begin, sizeof(int)*(end-begin+1));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc failed\n");
		return;
	}
	_MergeSort(a,0,n-1,tmp);
	free(tmp);
}

2、非递归版(改循环)

void MergeSortNonR(int* a,int n) 
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc failed\n");
		return;
	}
	int gap = 1;//gap是每组数据个数
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)  
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			if (end1>=n || begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			memcpy(a+i, tmp+i, sizeof(int)*(end2-i+1)); 
		}
		gap *= 2;
	}
	free(tmp);
}

八、计数排序

如{100,105,105,101,102,100,109,100}

用计数排序的思想做的话就是:

1、找出最大最小的数,(100和109)

2、记录{100,101,102,103,104,105,106,107,108,109}中每个数出现的次数,并用countA数组存起来(此例countA={2,1,1,0,0,2,0,0,0,1})

3、根据countA就可以排出顺序。

void Sort(int* a, int n)
{
	//找最大最小
	int min = a[0], max = a[0];
	int i;
	for (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* countA = (int*)calloc(range, sizeof(int));//开一个用来计数的数组
	if (countA == NULL)
	{
		perror("calloc failed");
		return;
	}
	for (int i = 0; i < n; i++)//遍历原数组
	{
		countA[a[i] - min]++;//a[i]-min就是a[i]对应在count的位置
	}
	//放回去
	int j = 0;
	for (int i = 0; i < range; i++)//遍历countA数组
	{
		while (countA[i]--)//同样的数出现的次数
		{
			a[j++] = i + min;
		}
	}
    free(countA);
}

显然,计数排序不适合进行数据分散的排序;相反,若数据比较集中,计数排序的效率是很高的

九、补充(稳定性)

简单来说,就是看相同的数据在排序前后的相对位置有没有改变,

相对位置不变,则稳定;

相对位置改变,则不稳定;

如冒泡排序,若待排序列为132445,由于两个4相等,所以前后位置并没有改变,因此冒泡排序是稳定的。

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

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

相关文章

spring常用注解(全)

一、前言 Spring的一个核心功能是IOC&#xff0c;就是将Bean初始化加载到容器中&#xff0c;Bean是如何加载到容器的&#xff0c;可以使用Spring注解方式或者Spring XML配置方式。 Spring注解方式减少了配置文件内容&#xff0c;更加便于管理&#xff0c;并且使用注解可以大大…

Vue学习笔记(6. 组件之间传值)

1. 组件基本语法 (1) template (2) script (3) style 2. 父组件传值&#xff0c;子组件接值 (1) 父组件传值给子组件 (2) 子组件接收父组件的传值&#xff08;随时接收&#xff09; 子组件的值会随着父组件值的变更而变更。但是子组件变更&#xff0c;不会影响父组件的数据…

Jetson nano部署剪枝YOLOv8

目录前言一、YOLOv8模型剪枝训练1. Pretrain[option]1.1 项目的克隆1.2 数据集1.3 训练2. Constraint training3. Prune4. finetune二、YOLOv8模型剪枝部署1. 源码下载2. 环境配置2.1 trtexec环境变量设置3. ONNX导出3.1 Transpose节点的添加3.2 Resize节点解析的问题4. 运行4.…

【RabbitMQ学习日记】—— 发布确认与交换机

一、发布确认 1.1 发布确认的原理 生产者将信道设置成 confirm 模式&#xff0c;一旦信道进入 confirm 模式&#xff0c;所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始)&#xff0c;一旦消息被投递到所有匹配的队列之后&#xff0c;broker就会发送一个确认…

小白快速学习Markdown

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

做好Python工程师,首先你需要做好的几件事

做好Python工程师&#xff0c;需要做好的几件事&#xff0c;我想分享给大家。首先千万不要做事周折。在你提问之前&#xff0c;先好好想一想&#xff0c;这个问题自己能不能解决。如果能解决&#xff0c;尽量自己解决&#xff1b;如果解决不了&#xff0c;那就要把你的问题描述…

大宗商品进口管理软件可以帮助企业解决哪些?

什么是大宗商品贸易&#xff1f;简单讲就是大宗商品在国际上自由流通。以贸易的形式&#xff0c;把商品从价格低的地方拉到价格高的地方出售。大宗商品是指可进入流通领域&#xff0c;可在工农业领域生产与消费使用的大批量买卖的。主要包括的类别有&#xff1a;能源商品、基础…

网络编程答疑融合连环tcp/nio/bio/redis/redisson/lettuce/netty/dubbo

如果有不对的地方, 欢迎在评论区指正: bio 1.1 请求-响应模型. 对于接收方, serverSocket.accept() 为每个请求(连接)安排一个线程 1.2浪费(阻塞占比大): socket.getInputStream().read()调用是阻塞的, 实际情况对于常见的web应用, 大家都是长连接, 同一时刻, 阻塞在此在线程会…

蓝牙技术|苹果获空间音频新专利,AirPods可动态调整声学输出

美国商标和专利局&#xff08;USPTO&#xff09;公示的清单显示&#xff0c;苹果在近日获得了一项名为“测定虚拟聆听环境”的新专利。据悉&#xff0c;该技术可以改善用户的聆听体验&#xff0c;增强空间音频的沉浸感&#xff0c;未来有望应用在AirPods上。 这项专利技术可以…

第二章 Linux目录结构

第二章 Linux目录结构linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录“/”&#xff0c;然后在此目录下再创建其他的 目录。 2)深刻理解 linux 树状文件目录是非常重要的。3)记住一句经典的话:在Linux世界里&#xff0c;一切皆文件(!!)4)示意…

4.12--计算机网络之TCP篇之TCP 协议的缺陷+如何基于 UDP 协议实现可靠传输?--(复习+大总结)---沉下心来(加油呀)

TCP 协议四个方面的缺陷&#xff1a; 1.升级 TCP 的工作很困难&#xff1b; TCP 协议是在内核中实现的&#xff0c;应用程序只能使用不能修改&#xff0c;如果要想升级 TCP 协议&#xff0c;那么只能升级内核。 而升级内核这个工作是很麻烦的事情 2.TCP 建立连接的延迟&#x…

Linux -- 进程间通信

文章目录1. vscode软件下载和使用1.1 下载1.1.1 解决下载慢问题1.1.2 推荐下载链接1.2 vscode是什么1.3 Windows本地vscode使用1.4 远程连接linux1.5 推荐插件2. 进程间通信目的3. 为什么需要通信4. 匿名管道4.1 原理4.2 代码案例4.3 玩一玩(进程池)4.3.1 模型4.3.2 代码5. 命名…

STM32+W5500实现以太网通信

STM32系列32位微控制器基于Arm Cortex-M处理器&#xff0c;旨在为MCU用户提供新的开发自由度。它包括一系列产品&#xff0c;集高性能、实时功能、数字信号处理、低功耗/低电压操作、连接性等特性于一身&#xff0c;同时还保持了集成度高和易于开发的特点。本例采用STM32作为MC…

【开懂C++】命名空间 函数重载 缺省参数

目录一.命名空间二.缺省参数三.函数重载一.命名空间 在编写C语言代码时&#xff0c;当工程较大时&#xff0c;很容易产生变量命名冲突的情况——一般有两种冲突的情况 1.变量名与库中的函数名、关键字冲突。2.工程模块化搭建时不同文件的命名冲突。 而C为了优化这一缺陷&#…

安装Ubuntu系统后的实用工具配置指南

1. 修改软件源 Ubuntu 默认的软件源是境外的&#xff0c;速度上会有些问题&#xff0c;我们可以在Software & Updates(软件和更新)中选择国内的镜像。 一般我们选择清华源或者阿里云源。 2. 安装chorme浏览器 在ubuntu下我比较习惯用火狐浏览器和谷歌浏览器。 谷歌浏览…

vue 自定义指令directive的使用场景

1. 一个指令定义对象可以提供如下几个钩子函数(均为可选) bind:只调用一次&#xff0c;指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted:被绑定元素插入父节点时调用(仅保证父节点存在&#xff0c;但不一定已被插入文档中)。update:只要当前元素不被…

Leetcode.1971 寻找图中是否存在路径

题目链接 Leetcode.1971 寻找图中是否存在路径 easy 题目描述 有一个具有 n 个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi]表示顶点 ui和顶…

关于maxwell

这里写目录标题什么是Maxwell如何使用MaxwellMaxwell是一个mysql二进制binlog日志分析工具&#xff0c;Java语言编写&#xff0c;功能十分强大&#xff0c;可以将日志转换成json并发送到kafka&#xff0c;redis&#xff0c;rabbitmq等中间组件&#xff0c;因为最近在理解怎样在…

QtSqlite加密--QtCipherSqlitePlugin的使用

文章目录QtSqlite加密第一步&#xff1a;环境准备第二步&#xff1a;连接数据库第三步&#xff1a;数据库操作第四步&#xff1a;使用新的可视化工具查看数据库数据QtSqlite加密 上次说了QxOrm的数据库连接、映射和基础的增删改查&#xff0c;但是我们在使用数据库的时候并不希…

期刊论文图片代码复现【由图片还原代码】(OriginMatlab)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…