常见排序算法

目录

一、插入排序

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.mfbz.cn/a/12336.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

树上差分(点差分/边差分)

树上差分一般有两种类型的题目&#xff0c;一种是对边进行差分&#xff0c;另一种就是对点进行差分。 对应的操作也有两种&#xff0c;对边进行差分的对应操作就是给定一对节点(u,v)&#xff0c;让我们把u到v之间路径上的边权都加val&#xff0c;对点进行差分的对应操作就是给…

MYSQL数据库

目录 SQL SQL-DDL 操作数据库 查询&#xff08;show&#xff09;&#xff08;select&#xff09; 创建&#xff08;create&#xff09; 删除&#xff08;drop&#xff09; 操作表 查询当前数据库所有表 修改表 删除 SQL-DML 添加数据&#xff08;可以批量添加&…

课程简介:.Net Core从零学习搭建权限管理系统

课程简介目录 &#x1f680;前言一、课程背景二、课程目的三、系统功能四、系统技术架构五、课程特点六、课程适合人员七、课程规划的章节八、最后 &#x1f680;前言 本文是《.Net Core从零学习搭建权限管理系统》教程专栏的导航站&#xff08;点击链接&#xff0c;跳转到专栏…

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

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

亿发软件:传统食品饮料批发行业如何通过信息化管理系统降本增效?

传统食品饮料批发行业信息化水平较低&#xff0c;存在多重管理难题&#xff0c;例如&#xff1a; 手动数据输入和管理&#xff0c;导致错误和效率低下&#xff1b; 数据缺乏实时可见性&#xff0c;无法实时了解企业仓库存量、销售额和其他关键业务指标&#xff1b; 低效的供应链…

索引:索引知识重复习,什么是索引、索引的类型、建立索引及【最左匹配原则】、Explain查看sql的执行计划

文章目录 什么是索引索引的类型主键索引&#xff08;primary key&#xff09;普通索引&#xff08;index&#xff09;复合索引全文索引&#xff08;fulltext&#xff09;空间索引唯一索引索引修改及删除 Explain一、using filesort(减慢查询效率)二、Using temporary三、using …

前端UI框架有哪些|20个优秀免费开源的WEB前端UI框架提高网站开发效率

最近准备学习一下前端UI我也是在网上找了很久最终整理出来了20个不错的前端UI框架网站,大家都知道很多成熟的前端框架可以直接引,学习框架可以提升我们网站的开发速度。有些大型公司的前端或者后端框架都是用自己开发的,对于大部分用户和公司来讲,我们可以用开源免费的前端…

Python 中 SyntaxError: ‘yield‘ outside function 错误

当我们在函数外部使用 yield 关键字时&#xff0c;会出现 Python “SyntaxError: ‘yield’ outside function”。 要解决该错误&#xff0c;如果我们需要对每个元素执行一些运算符&#xff0c;请使用列表理解&#xff0c;或者缩进函数内部使用 yield 的代码。 下面是一个产生…

毕业2年,跳槽到下一个公司就25K了,厉害了···

本人本科就读于某普通院校&#xff0c;毕业后通过同学的原因加入软件测试这个行业&#xff0c;角色也从测试小白到了目前的资深工程师&#xff0c;从功能测试转变为测试开发&#xff0c;并顺利拿下了某二线城市互联网企业的Offer&#xff0c;年薪 30W 。 选择和努力哪个重要&a…

写博客8年与人生第一个502万

题记&#xff1a;我们并非生来强大&#xff0c;但依然可以不负青春。 原本想好好写一下如何制定一个目标并通过一点一滴的努力去实现&#xff0c;这三年反思发现其实写自己的经历并不重要。 很多人都听过一句话&#xff1a;榜样的力量是无穷的。 更现实和实际的情况是&#x…

mysql聚合函数

文章目录 前言一、常见的聚合函数1.avg和sum函数2.max和min函数3.count函数 二、group by的使用1.基本使用方法2.with rollup 求平均值 三、having关键字的使用四、多表连接聚合函数1.sql92语法总结2.sql99语法总结 总结 前言 聚合函数&#xff1a;他是对一组数据进行汇总的函…

3 个自定义防抖 Hooks 的实现原理

前言— 本文通过实现 useDebounceFn、useDebounce、useDebounceEffect 3 种自定义防抖 Hooks&#xff0c;来介绍在日常开发过程中自定义 Hooks 的思路及实现&#xff0c;帮助大家完成通用 Hooks 来提高开发效率。 防抖— 防抖的概念已经司空见惯了&#xff0c;这里稍作简单介…

Golang每日一练(leetDay0034) 二叉树专题(3)

目录 100. 相同的树 Same Tree &#x1f31f; 101. 对称二叉树 Symmetric Tree &#x1f31f; 102. 二叉树的层序遍历 Binary Tree Level-order Traversal &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一…

程序设计方法学

体育竞技分析 问题分析 体育竞技分析 需求&#xff1a;毫厘是多少&#xff1f; 如何科学分析体育竞技比赛&#xff1f; 输入&#xff1a;球员的水平 输出&#xff1a;可预测的比赛成绩 体育竞技分析&#xff1a;模拟N场比赛 计算思维&#xff1a;抽象 自动化 模拟&am…

【算法题】2583. 二叉树中的第 K 大层和

题目&#xff1a; 给你一棵二叉树的根节点 root 和一个正整数 k 。 树中的 层和 是指 同一层 上节点值的总和。 返回树中第 k 大的层和&#xff08;不一定不同&#xff09;。如果树少于 k 层&#xff0c;则返回 -1 。 注意&#xff0c;如果两个节点与根节点的距离相同&…

能够翻译文档的免费软件-免费翻译整个文档的软件

chatgpt怎么实现批量翻译 ChatGPT是一种基于人工智能技术的自然语言处理软件&#xff0c;可以实现快速、准确的批量翻译操作&#xff0c;同时也支持多种语言翻译。下面是 ChatGPT 的批量翻译操作流程&#xff1a; 步骤 1: 确定翻译语言和翻译文本 首先需要确定要翻译的原文本…

java学习之局部内部类

目录 一、内部类简介 二、内部类的分类 三、局部内部类 第一点 第二点 第三点 第四点 第五点 第六点 第七点 一、内部类简介 类的五大成员&#xff1a;属性、方法、构造器、代码块、内部类 package com.hspedu.innerclass;public class InnerClass01 {public static…

AOP与SpringBoot使用AOP实例

AOP&#xff1a;Aspect Oriented Programming&#xff08;面向切面编程、面向方面编程&#xff09;&#xff0c;其实就是面向特定方法编程。 动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术&#xff0c;旨在管理bean对象的过程中&#xff0c;主要通过…

Windows使用Dockers+battery historian踩坑记

1、首先&#xff0c;需要翻墙。 2、然后安装Dockers&#xff0c;网上好多博客说安装Docker Toolbox&#xff0c;我亲测无效&#xff0c;卸载后安装Docker for Windows&#xff0c;安装完成后打开&#xff0c;会提示&#xff1a; Hardware assisted virtualization and data e…

Mybatis03学习笔记

目录 使用注解开发 设置事务自动提交 mybatis运行原理 注解CRUD lombok使用&#xff08;偷懒神器&#xff0c;大神都不建议使用&#xff09; 复杂查询环境&#xff08;多对一&#xff09; 复杂查询环境&#xff08;一对多&#xff09; 动态sql环境搭建 动态sql常用标签…
最新文章