数据结构_手撕七大排序(快排,归并,堆排,希尔,选择,插入,冒泡)

✨✨所属专栏:数据结构✨✨

✨✨作者主页:嶔某✨✨

排序的概念

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

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

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

function/数据结构-排序算法 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com) 

常见排序算法 

插入排序 

示意图:

 排序过程:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。

 时间复杂度:

O(n^2) 

代码实现: 

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

希尔排序

示意图:

排序过程:

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

  • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列个数k,对序列进行k 趟排序;
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

 随着增量因子(gap)的减少,这其实是一个使数组逐渐变有序的过程。那么有个问题,gap如何变化才能使算法得到最大的优化呢?

gap越大,大的数可以越快跳到后面,小的数可以越快跳到前面,越不接近有序;gap越小,跳的越慢,但是越接近有序。当 gap == 1时就相当于插入排序。

这个算法的发明者用的是gap /= 2,有人曾今算过(这里需要很高的数学水平,我还是算了)gap = gap / 3 + 1,为最优解。加一是为了让最后一趟排序的gap为1。

时间复杂度:

第一趟排序分为gap = n / 3组(忽略+1),每组三个数据,最坏情况就是逆序,向前调整(1+2)次,第一趟就的消耗是 n。第二趟gap = n / 9 组每组 9 个数据,最坏情况调整(1+2+......+7+8)次,第二趟的消耗是 4n。

但是,第二趟以后的每一趟都不是最坏的情况,所以第二趟的消耗到不了4n,具体是多少?这里需要加一些概率的公式

最后一趟gap = 1,直接就是插入排序,消耗是n,根据这个我们大概画出了下图。

 最后这个排序的时间复杂度大约为:n^(1.3)

代码实现: 

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > a[end + gap])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
					break;
			}
			a[end + gap] = tmp;
		}
	}
}

选择排序

示意图:

排序过程:

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • 初始状态:无序区为R[1..n],有序区为空;
  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • n-1趟结束,数组有序化了。

另外我们在遍历时可以同时将无序区中最大的数和最小的数的下标分别记录,将最大数放在无序区的末尾,将最小数放在无序区的开头。

注意这里有个坑 :当一趟排序记录的mini == end时,将max处的数交换到end处,这是end处的数就是最大的数了,之后再将mini处的数交换到start处(这时mini处的数是最大的),这样就没有完成任务。

解决办法是:在交换之前,做一个判断更新mini的值。

时间复杂度:

O(n^2) 

代码实现:

//选择排序
void SelectSort(int* a, int n)
{
	int start = 0;
	int end = n - 1;
	while (end > start)
	{
		int mini = start;
		int maxi = end;
		for (int i = start; i <= end; i++)
		{
			if (a[mini] > a[i])
				mini = i;
			if (a[maxi] < a[i])
				maxi = i;
		}
		if (mini == end)
		{
			Swap(&a[maxi], &a[end]);
			mini = maxi;
			Swap(&a[mini], &a[start]);
		}
		else
		{
			Swap(&a[maxi], &a[end]);
			Swap(&a[mini], &a[start]);
		}
		end--;
		start++;
	}
}

堆排序

示意图:

排序过程:

这里参考下数据结构_堆的代码:function/数据结构-堆 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com)

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆(堆的元素个数减一),然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

时间复杂度:

O(n*log(n))

代码实现: 

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (a[child] < a[child + 1] && child + 1 < n)
			child++;
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}

//堆排序
void HeapSort(int* a, int n)
{
	for (int i = (n - 2) / 2; i >= 0; i--)
		AdjustDown(a, n, i);

	for (int i = n - 1; i > 0; i--)
	{
		Swap(&a[0], &a[i]);
		AdjustDown(a, i, 0);
	}
}

冒泡排序 

示意图:

排序过程:

  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。

时间复杂度:

O(n^2)

代码实现: 

//冒泡排序
void BubbleSort(int* a, int n)
{
	for (int i = n; i > 0; i--)
	{
		int prev = 0;
		int cur = 1;
		int falg = 1;
		while (cur < i)
		{
			if (a[prev] > a[cur])
			{
				falg = 0;
				Swap(&a[prev], &a[cur]);
			}
			prev = cur;
			cur++;
		}
		if (falg == 1)
			break;
	}
}

快速排序

示意图:

排序过程:

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

hoare: 

 单趟:我们定义一个key = left,右边先走,右边end--找小(比a[key]小),左边begin++找大,都找到了就交换,当begin > end跳出循环,再将a[key]和a[begin]交换。

为什么这里要右边先走?为什么相遇时位置比key小?

左边做key,右边先走,可以保证相遇位置比key小

场景:

L遇R:R先走,停下来,R停下条件是遇到比key小的值,一定比key小,L没有找大的,遇到R停下了

R遇L:R先走,找小,没有找到比key小的,直接跟L相遇了。L停留的位置是上一轮交换的位置,上一轮交换,把比key小的值换到L的位置了

相反:如果让右边做key,左边先走,i可以保证相遇位置比key要大

双指针:

单趟:定义一个prev,一个cur,cur找小,找不到小的prev和cur一起加加,找到了就把cur位置和prev++位置的值交换,prev和cur中间的值都是大的,将大的值一起往后挪。最后prev位置的值一定比key位置的值小,将prev和key位置的值交换。

每次递归分别传begin两侧的区间。

时间复杂度:

O(n*log(n))

代码实现:

(1)hoare版本:

//hoare版本
int partsort1(int* a, int left, int right)
{

	int begin = left, end = right;
	int x = Midofthree(a, left, right, (right + left) / 2);
	Swap(&a[x], &a[left]);
	int key = left;
	while (begin < end)
	{
		while (begin < end)
		{
			if (a[end] < a[key])
				break;
			end--;
		}
		while (begin < end)
		{
			if (a[begin] > a[key])
				break;
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[key], &a[begin]);
	return begin;
}

(2)双指针版本:

//双指针版本
int partsort2(int* a, int left, int right)
{
	int x = Midofthree(a, left, right, (right + left) / 2);
	Swap(&a[x], &a[left]);
	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[cur], &a[prev]);

		cur++;
	}
	Swap(&a[prev],&a[keyi]);
	return prev;
}

递归:

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);//小区间优化
		return;
	}

	int keyi = partsort1(a, left, right);

	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

非递归: 

这里不一定要用栈,也可以用队列来存储,如果你想,甚至可以用顺序表。栈只是为了更好的模拟递归的过程。

function/数据结构_栈 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com)

void QuickSortNonR(int* a, int left, int right)
{
	ST s;
	STInit(&s);
	STPush(&s, right);
	STPush(&s, left);

 	while (!STEmpty(&s))
	{
		int begin = STTop(&s);
		STPop(&s);
		int end = STTop(&s);
		STPop(&s);

		int mid = partsort1(a, begin, end);
		if (mid + 1 < end)//注意这里需判断
		{
			STPush(&s, end);
			STPush(&s, mid + 1);
		}
		if (begin < mid - 1)//注意这里需判断
		{
			STPush(&s, mid - 1);
			STPush(&s, begin);
		}
	}
}

归并排序

示意图:

排序过程:

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。 

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

时间复杂度:

O(n*log(n))

代码实现: 

void _MergeSort(int* a, int* tmp, int left, int right)
{
	if (left >= right)
		return;
	int mid = (left + right) / 2;
	_MergeSort(a, tmp, left, mid);
	_MergeSort(a, tmp, mid + 1, right);

	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	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 + left, tmp + left, sizeof(int) * (right - left + 1));
}


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

	free(tmp);
	tmp = NULL;
}

未完…… 

总结: 

本期博客到这里就结束了,如果有什么错误,欢迎指出,如果对你有帮助,请点个赞,谢谢!

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

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

相关文章

使用wheelnav.js构建酷炫的动态导航菜单

目录 前言 一、WheelNav是什么 1、项目地址 2、关于开源协议 3、相关目录介绍 二、如何使用wheelnav.js 1、新建html页面 2、设置style样式 3、创建展示元素实现动态导航 三、参数即方法介绍 1、参数列表 2、运行方法 3、实际成果 四、总结 前言 用户体验永远是一…

『 Linux 』目录与软硬链接 (万字详解)

文章目录 如何理解目录目录项 目录中的权限问题根目录Dentry缓存文件的增删改查与文件系统关系软硬链接软链接硬链接 如何理解目录 目录是一个文件存在其对应独立的Inode; $ stat dirFile: ‘dir’Size: 4096 Blocks: 8 IO Block: 4096 directory Device: f…

栈的最小值

题目链接 栈的最小值 题目描述 注意点 执行push、pop和min操作的时间复杂度必须为O(1) 解答思路 使用两个栈&#xff0c;一个栈deque存储栈中对应的元素值&#xff0c;另一个栈minDeque存储当前栈中所有元素的最小值&#xff0c;当执行push(int x)操作&#xff0c;deque直…

【乐吾乐3D可视化组态编辑器】数据接入

数据接入 本文为您介绍3D数据接入功能&#xff0c;数据接入功能分为三个步骤&#xff1a;数据订阅、数据集管理、数据绑定 编辑器地址&#xff1a;3D可视化组态 - 乐吾乐Le5le 数据订阅 乐吾乐3D组态数据管理功能由次顶部工具栏中按钮数据管理打开。 在新弹窗中选择数据订阅…

白银票据~

一. 白银票据的原理 白银票据就伪造ST票据&#xff0c; kerberoasting是破解ST票据中的服务用户hash值&#xff0c;有以下区别&#xff1a; 白银票据&#xff1a;伪造的ST使用的是机器用户的Hash值 Kerberoasting:破解的是ST的域用户的hash值二. 白银票据的利用条件 1.域名 …

基于线性回归根据饮食习惯和身体状况估计肥胖水平

目录 1. 作者介绍2&#xff0e;饮食习惯与身体状况数据集介绍3&#xff0e;实验步骤3.1 数据分析3.2 可视化处理数据3.3 导入线性回归模型进行训练3.4 预测结果3.5 完整代码3.5.1 数据分析3.5.2 模型评估 参考文献 1. 作者介绍 刘欢&#xff0c;女&#xff0c;西安工程大学电子…

MySQL经典练习50题(上)(解析版)

所有笔记、生活分享首发于个人博客 想要获得最佳的阅读体验&#xff08;无广告且清爽&#xff09;&#xff0c;请访问本篇笔记 MySQL经典练习50题&#xff08;上&#xff09; 创建数据库和表 -- 建 表 -- 学 生 表 CREATE TABLE Student( s_id VARCHAR(20), s_name VARCHAR(2…

ffmpeg视频编码原理和实战-(2)视频帧的创建和编码packet压缩

源文件&#xff1a; #include <iostream> using namespace std; extern "C" { //指定函数是c语言函数&#xff0c;函数名不包含重载标注 //引用ffmpeg头文件 #include <libavcodec/avcodec.h> } //预处理指令导入库 #pragma comment(lib,"avcodec.…

【linux】swap学习

在 Linux 系统中&#xff0c;swap 是一种用于扩展系统内存的技术。当物理内存&#xff08;RAM&#xff09;不足时&#xff0c;系统会将一部分不常用的内存数据移至 swap 空间&#xff0c;从而释放物理内存供其他程序使用。Swap 空间可以是一个单独的分区&#xff08;swap 分区&…

今日份动态规划学习(二维01背包+01背包变形)

目录 P1877 [HAOI2012] 音量调节 P1877 [HAOI2012] 音量调节 题解&#xff1a;一个入门级别的01背包问题&#xff0c;首先就是为什么能看出是01背包&#xff0c;因为只有两种状态&#xff0c;要不增大音量&#xff0c;要不减小音量&#xff0c;和01背包的选与不选非常近似。但…

学习笔记——IP地址网络协议——IPV4地址配置与应用

五、IPV4地址配置与应用 1、IP地址的基础配置命令 2、案例&#xff1a;配置接口IP地址 3、Loopback接口 Loopback接口∶用户需要一个接口状态永远是Up的接口的IP地址时&#xff0c;可以选择Loopback接口的IP地址。 Loopback接口一旦被创建&#xff0c;其物理状态和链路协议状…

消防认证-饰面型防火涂料

一、消防认证 消防认证是指消防产品符合国家相关技术要求和标准&#xff0c;且通过了国家认证认可监督管理委员会审批&#xff0c;获得消防认证资质的认证机构颁发的证书&#xff0c;消防产品具有完好的防火功能&#xff0c;是住房和城乡建设领域验收的重要指标。 二、认证依据…

【Linux】进程(5):命令行参数

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解Linux进程&#xff08;5&#xff09;&#xff1a;命令行参数&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 &#xff08;A&#xff09;为什么要有命令…

JavaWeb基础(JQuery,XML及解析)

这个阶段有点拖沓了&#xff0c;因为事情比较多&#xff0c;耽搁了一段时间&#xff0c;学习的主要内容为JQuery和XML&#xff0c;因为vue的出现&#xff0c;JQuery技术现在已经不流行了&#xff0c;但是不流行不代表我不会&#xff0c;JQuery最最最最核心的就是他的$()核心函数…

阿里云sls 采集日志安装记录

参考阿里云给的安装文档 阿里云安装Logtail组件 注意这里&#xff0c;选择地域&#xff0c;是中国地域选中国&#xff0c;海外选海外即可 按照文档继续下去 修改配置文件./alibaba-cloud-log-all/values.yaml 所有的操作完成后&#xff0c;去控制台配置 以上操作的前提是…

数据可视化---使用matplotlib绘制高级图表(2)

题目一&#xff1a;绘制人口金字塔图 编写程序。根据第8.6&#xff0c;绘制如下图的人口金字塔图。 运行代码&#xff1a; #绘制人口金字塔图 import numpy as np import pandas as pd import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] SimHei plt.rcParams[…

交互式流程图组件DHTMLX Diagram v6.0 - 拥有更灵活的高度可定制功能

DHTMLX Diagram库允许用几行代码构建JavaScript流程图&#xff0c;通过自动布局和实时编辑器&#xff0c;它可以更容易地将复杂数据可视化到一个整洁的层次结构中。 DHTMLX Diagram v6.0版本发布&#xff0c;带来了众多令人兴奋的新功能和改进&#xff0c;使得这个JavaScript图…

【SITS_CC】卫星图像时间序列的变化字幕(IEEE GRSL)

摘要 Satellite images time series (SITS) 提供了一种有效的方法来同时获取地球上观测区域的时间和空间信息。然而&#xff0c;传统的遥感CD方法的输出是二进制图或语义变化图&#xff0c;往往难以被最终用户解释&#xff0c;传统的遥感图像变化字幕方法只能描述双时图像。提…

ARM32开发——串口输出

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 需求串口数据发送串口打印实现复用功能串口发送流程&#xff08;了解&#xff09;串口的标志位关心的内容 需求 串口循环输出内容到…

AI生成PPT:一键式演示文稿制作的秘诀

工欲善其事&#xff0c;必先利其器。 随着AI技术与各个行业或细分场景的深度融合&#xff0c;日常工作可使用的AI工具呈现出井喷式发展的趋势&#xff0c;AI工具的类别也从最初的AI文本生成、AI绘画工具&#xff0c;逐渐扩展到AI思维导图工具、AI流程图工具、AI生成PPT工具、AI…