C语言-排序

常见的排序算法分为以下四种,插入排序,选择排序,交换排序,归并排序。

一、插入排序

(一)直接插入排序

直接插入排序,将一段数组看做被分成已排序序列和未排序序列,排序过程是从未排序序列的元素开始,该元素与已排序序列的元素从后向前扫描,找到第一个小于(或大于)该元素的已排序项,然后将该未排序项插入到已排序序列中的适当位置。

void InsertSort(int* a, int n)
{
	//最后一组,[0, n-2]
	for (int i = 0; i < n - 1; i++)//是n不是n-1!!!
	{
		int end = i;
		int tmp = a[end + 1];
		//[0,end]是有序的,从end+1开始插入
		while (end >= 0)
		{
			if (tmp < a[end])//比tmp值大的,往后挪
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				//不在里面放是因为,如果要插入的,比所有值都小
				//end此时为-1,循环结束,跳不到else
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

(二)希尔排序

希尔排序是针对直接插入排序的改进

将数组元素分为gap组,即每隔gap个的元素为一组,组内排序,然后修改gap值,当gap为1时,就是直接插入排序

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//gap = gap / 2;//除几都可以,除3比较好,但是最后不能保证是1
		gap = gap / 3 + 1;//这样就保证了最后结果一定是1
		for (size_t 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];
					end -= gap;
				}
				else
					break;
			}
			a[end + gap] = tmp;
		}
		OP()把打印注释
		//printf("gap:%2d->", gap);
		//PrintArray(a, n);
	}
}

二、选择排序

(一)选择排序

选择排序,遍历整个数组,每次选择出最大的

进行升级,每次遍历找出最小最大的

注意下面代码中的
        if (begin == maxi)
            maxi = mini;

这个是很有必要的

比如说begin开始是最大值,和mini交换之后,接下来最大maxi要和begin进行交换,但是begin此时的值不是最大值

void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini = begin, maxi = begin;
		for (int i = begin + 1; i < end; i++)//从begin+1是因为,自己跟自己比没意义,<end也是
		{
			if (a[i] > a[maxi])
				maxi = i;
			if (a[i] < a[mini])
				mini = i;
		}
		Swap(&a[begin], &a[mini]);
		if (begin == maxi)
			maxi = mini;
		Swap(&a[end], &a[maxi]);
		++begin;
		--end;
	}
}

(二)堆排序

void AdjustDown(int* a, int n, int parent)
{
	// 先假设左孩子小
	int child = parent * 2 + 1;

	while (child < n)  // child >= n说明孩子不存在,调整到叶子了
	{
		// 找出小的那个孩子
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++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)
{
	// 向下调整建堆 O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	// O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

三、交换排序

(一)冒泡排序

两层循环,内层单趟是找出最大的

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		//里面是单趟
		int flag = 0;
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				Swap(&a[j - 1], &a[j]);
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}

(二)快速排序

1、hora方法

以第一个数为key,将比key小的元素放在基准元素的左边,将比key大的元素放在基准元素的右边。

遍历结束后,key左边所有的都比key小,右边都比key大,key就排序完成了,然后在递归调用快排函数排序左右两侧。

但是这样有个问题,假如数组一开始就有序,那么排序的深度很大(需要递归多次),这就可以使用三数取中来优化代码,left,right,以及(left+right)/2这三个是数的下标,找到中间值给left进行交换。

同时,快排的递归调用类似于满二叉树,后三层递归次数占总次数很大,可以使用小区间优化,当该区间元素个数不够时,使用一种排序方法排序,这里选择直接插入排序,(直接插入排序在小规模数据上表现良好)。

int GetMidi(int* a, int left, int right)
{
	//取得是大小在中间的值
	int midi = (left + right) / 2;
	if (a[left] < a[midi])
	{
		if (a[midi] < a[right])
			return midi;
		else if (a[left] < a[right])//走到这里说明a[left] < a[midi], a[right] < a[midi]
			return right;
		else
			return left;
	}
	else//a[left] >= a[midi]
	{
		if (a[midi] > a[right])
			return midi;
		else if (a[left] < a[right])
			return left;
		else
			return right;
	}
}
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	// 小区间优化,不再递归分割排序,减少递归的次数
	if ((right - left + 1) < 10)
	{
		InsertSort(a + left, right - left + 1);//a+left是因为InsertSort需要数组起始地址
	}
	else
	{
		// 三数取中
		int midi = GetMidi(a, left, right);
		Swap(&a[left], &a[midi]);

		int keyi = left;
		int begin = left, end = right;
		while (begin < end)
		{
			// 右边找小
			while (begin < end && a[end] >= a[keyi])
			{
				--end;
			}

			// 左边找大
			while (begin < end && a[begin] <= a[keyi])
			{
				++begin;
			}

			Swap(&a[begin], &a[end]);
		}//while运行完,左边全是比key小,右边全是比key大

		Swap(&a[keyi], &a[begin]);
		keyi = begin;
		// [left, keyi-1] keyi [keyi+1, right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
}

2、前后指针方法

prev和cur

cur先向右移动,找到比key小的,然后++prev,交换prev和cur的值

遍历一遍,prev最后位置左边,都是比key值小的,跟Quick相似,左边全比他小,右边比key大

int PartSort2(int* a, int left, int right)
{
	 三数取中
	//int midi = GetMidi(a, left, right);
	//Swap(&a[left], &a[midi]);

	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)//还在区间
	{
		//由于一开始紧挨着,后面判断是为了防止自己交换
		//顺序也不能反,如果a[cur] > a[keyi],就不会执行后面的,也就是说cur++但是prev不++
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		//不要写else,cur无论什么情况都要++
		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);//a+left是因为InsertSort需要数组起始地址
	}
	else
	{
		int keyi = PartSort2(a,left,right);//修改此处即可
		// [left, keyi-1] keyi [keyi+1, right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
}

3、非递归方法

快排非递归 -- 数据用栈模拟(队列也可以,队列先进先出,数据就不用倒着进)
 用栈类似于DFS(深度优先遍历)
 用队列类似于BFS(广度优先遍历 -- 二叉树的广度就是层序遍历)
递归会建立栈帧,(溢出跟深度有关)
在递归里,栈帧存的核心数据是 -- 区间(所以非递归方式,栈存放区间)
循环每走一次,取栈顶区间,进行单趟排序,右左子区间入栈(右左是因为栈后进先出)
 函数调用是在栈区取空间(栈只有8M)
 数据结构的栈空间不够去扩容是在堆(堆在32位下是2G)

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

	while (!STEmpty(&st))// 循环每走一次,相当于一次递归
	{
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);

		int keyi = PartSort2(a, begin, end);
		// [begin, keyi - 1] keyi [keyi + 1, end]
		// 先入右[keyi + 1, end]
		if (keyi + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}
		//再入左[begin, keyi - 1]
		if (begin < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}
	}
}

四、归并排序

假设把数组分成两段,如果左右区间有序,两个指针指向左右区间第一个,一个比另一个小,就比所有的小
把小的尾插到一个tmp数组
类似二叉树的后序,有序就归并,无序就递归

1、递归

void _MergeSort(int* a, int* tmp, int begin, int end)//_从习惯和风格上是子函数
{
	//只有一个值,返回
	if (begin >= end)
		return;

	int midi = (begin + end) / 2;
	//[begin , midi] [midi + 1, end] -- 有序就可以进行归并
	//不能是[begin , midi - 1] [midi, end]!!!
	//假设begin=0,end=1,此时midi=0,如果-1得到的分组就是[0,-1],[0,1]
	//[偶数,偶数+1]分组得到,[偶数,偶数],[偶数,偶数+1] -- [偶数,偶数+1]一直出现,造成栈溢出x
	
	//子问题递归来实现有序
	_MergeSort(a, tmp, begin, midi);
	_MergeSort(a, tmp, midi + 1, end);

	//归并
	int begin1 = begin, end1 = midi;
	int begin2 = midi+1, end2 = end;
	int i = begin;
	//有一个满足是结束的条件,while循环里要写继续的条件!!!
	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++];

	//数据还在tmp数组中,需要拷贝回去memcpy
	//不是拷整个区间,是begin到end
	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail!");
		return;
	}

	_MergeSort(a, tmp, 0, n-1);

	free(tmp);
	tmp = NULL;
}

2、非递归

 使用非递归 -- 一开始每两个数归并,然后每四个数归并...把递归倒着看
 gap -- 每组归并数据的数据个数(gap是其中一组归并数据的个数)
 gap, gap 这样归并,所以数据个数*2
 [i, i + gap -1], [gap + i,i + 2*gap - 1]

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)//i代表每组归并的起始位置
		{
			//[begin1,end1][begin2,end2]
			//end = 起点 + 个数 - 1
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;
			printf("[%d, %d] [%d, %d]", begin1, end1, begin2, end2);

			//第二组都越界 -- [begin1,end1] n [begin2,end2]
			if (begin2 >= n)
			{
				break;
			}

			//第二组begin2没越界,end2越界,需要修正一下继续归并 -- [begin1,end1][begin2, (n) end2]
			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, (end2 - i + 1) * sizeof(int));
		}
		gap *= 2;
		printf("\n");
	}
	free(tmp);
	tmp = NULL;
}

五、各个排序的时间复杂度及稳定性

稳定性指的是相同的值排完后,相对顺序不变

直接插入排序

两层循环

时间复杂度O(N^2),每次插入大约移动一半元素

最坏时间复杂度O(N^2),数组完全逆序

稳定,因为插入操作不会改变相同元素的相对顺序

希尔排序

时间复杂度O(N^1.3) - O(N^2),O(N^1.3)即可

最坏时间复杂度O(N^2),取决于分组情况gap

不稳定,元素的移动可能跨越多个间隔,导致相同元素相对顺序改变

选择排序

时间复杂度O(N^2),每次插入大约移动一半元素

最坏时间复杂度O(N^2),数组完全逆序

不稳定,每次选择最小元素并交换,会改变相同元素的相对顺序

堆排序

时间复杂度O(nlogn),

最坏时间复杂度O(nlogn),

由于堆的调整操作,无论输入数组的初始状态如何,时间复杂度不变

不稳定,在调整堆的过程中,会交换元素,可能改变相同元素的相对顺序

冒泡排序

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

最坏时间复杂度O(N^2),当数组完全逆序时

稳定,相邻元素比较和交换,相同元素不会交换位置

快速排序

时间复杂度O(nlogn),

最坏时间复杂度O(N^2),当每次选择的基准元素都为当前子数组中的最大或最小元素时,导致划分极不均衡,递归深度达到最大

不稳定,分区过程中,基准元素的交换可能导致相同元素相对顺序改变

归并排序

时间复杂度O(nlogn),

最坏时间复杂度O(nlogn),

无论数组初始状态如何,通过不断将数组对半划分并合并,所以其时间复杂度不会变

稳定,在合并过程中,可以保证相同元素的相对顺序不变

    while (begin1 <= end1 && begin2 <=end2)
    {
        if (a[begin1] <= a[begin2])
            tmp[i++] = a[begin1++];
        else
            tmp[i++] = a[begin2++];
    }

if中的<=,=可以确保相同值时相对顺序不变(递归非递归都是这个=)

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

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

相关文章

Chrome webdriver下载-避坑

WebDriver以原生的方式驱动浏览器&#xff0c;不需要调整环境变量。 一、window版 1.chrome和chromedriver下载地址&#xff1a; Chrome for Testing availability 我下载的是如下两个安装包&#xff0c;解压即可。 2.导包 pip install selenium然后用python代码引用即可…

【卷积神经网络】LeNet实践

模型建立 数据初始化根据模型搭建前向传播打印模型结构 前向传播数据初始化 def __init__(self):super(LeNet, self).__init__()# 第一层卷积层&#xff1a;# 输入&#xff1a;灰度图像 (1通道&#xff0c;大小 28x28)# 输出&#xff1a;6个特征图 (大小 28x28, 通过padding2保…

51c~Pytorch~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/11878447 一、PyTorch与torch-xla的桥接 文章从XLATensor开始的溯源、注册PyTorch库实现、从PyTorch调用到torch_xla三个方面来介绍PyTorch与torch-xla的桥接 XLA (Accelerated Linear Algebra)是一个开源的机器学习编…

五大短视频平台变现方式

重新整理了五个短视频平台的平台特性&#xff0c;用户分析、年龄段、用户量级和各个平台的变现方式。想在这几个平台赚&#x1f4b0;的可以多看看&#xff0c;有没有适合自己的变现方式⚡ 五个短视频平台&#xff1a; 抖音、快手、哔哩哔哩、视频号、小红书

开源Java快速自测工具,可以调用系统内任意一个方法

java快速测试框架&#xff0c;可以调到系统内任意一个方法&#xff0c;告别写单测和controller的困扰。 开源地址&#xff1a;https://gitee.com/missyouch/Easy-JTest 我们在开发时很多时候想要测试下自己的代码&#xff0c;特别是service层或者是更底层的代码&#xff0c;就…

数据结构开始——时间复杂度和空间复杂度知识点笔记总结

好了&#xff0c;经过了漫长的时间学习c语言语法知识&#xff0c;现在我们到了数据结构的学习。 首先&#xff0c;我们得思考一下 什么是数据结构&#xff1f; 数据结构(Data Structure)是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种或多种特定关系的数据元素…

Linux USB开发整理和随笔

目录 1 概述 2 硬件原理基础 2.1 USB发展 2.2 USB的拓扑 2.3 硬件接口 2.4 USB总线协议 2.4.1 通信过程 2.4.2 概念关系 2.4.3 管道PIPE 2.4.4 传输 2.4.5 事务 2.4.6 包结构与类型 2.4.6.1 令牌包 2.4.6.2 数据包 2.4.6.3 握手包 2.5 描述符 2.5.1 设备描述符…

一键找出图像中物体的角点

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

考研数学【线性代数基础box(数二)】

本文是对数学二线性代数基础进行总结&#xff0c;一些及极其简单的被省略了&#xff0c;代数的概念稀碎&#xff0c;不如高数关联性高&#xff0c;所以本文仅供参考&#xff0c;做题请从中筛选&#xff01; 本文为初稿&#xff0c;后面会根据刷题和自己的理解继续更新 第一章…

警惕!手动调整服务器时间可能引发的系统灾难

警惕&#xff01;手动调整服务器时间可能引发的系统灾难 1. 鉴权机制1.1 基于时间戳的签名验证1.2 基于会话的认证机制&#xff08;JWT、TOTP&#xff09; 2. 雪花算法生成 ID 的影响2.1 时间戳回拨导致 ID 冲突2.2 ID 顺序被打乱 3. 日志记录与审计3.1 日志顺序错误3.2 审计日…

【Linux】Systemtap在CentsOS9上测试成功了

在Ubuntu上测试没有成功&#xff0c;先看看运行成功的效果吧&#xff1a; 看到运行的效果&#xff0c;可以安心些&#xff0c;哈哈 指导操作来源于这里&#xff1a;SystemTap 主要来源于这里&#xff1a; https://sourceware.org/systemtap/SystemTap_Beginners_Guide/using-s…

【EthIf-03】 EthernetInterface软件栈的文件组织结构

上图为《AUTOSAR_SWS_EthernetInterface【v2.2.0 】》给出的EthernetInterface软件栈的文件组织结构,本文主要关注arccore代码中已存在的文件的功能和作用,不知道的小伙伴可以查看🔗EthIf的文件结构中的src和inc目录下的文件有哪些: 1. 文件结构 1.1 EthIf_Cbk.h 头文…

【LeetCode刷题之路】622.设计循环队列

LeetCode刷题记录 &#x1f310; 我的博客主页&#xff1a;iiiiiankor&#x1f3af; 如果你觉得我的内容对你有帮助&#xff0c;不妨点个赞&#x1f44d;、留个评论✍&#xff0c;或者收藏⭐&#xff0c;让我们一起进步&#xff01;&#x1f4dd; 专栏系列&#xff1a;LeetCode…

基于windows环境使用nvm安装多版本nodejs

目录 前言 一、卸载node 二、nvm是什么&#xff1f; 三、nvm安装 1.官网下载 nvm 包 2. 安装nvm-setup.exe 3. 配置路径和下载镜像 4. 检查安装是否完成 四、 使用nvm安装node 五、修改npm默认镜像源为淘宝镜像 六、环境变量配置 1. 新建目录 2. 设置环境变量 七…

Neo4j+Neovis+Vue3:前端连接数据库渲染

Neovis&#xff08;github&#xff09;&#xff1a;https://github.com/neo4j-contrib/neovis.js Neovis配置文档&#xff1a;neovis.js (neo4j-contrib.github.io) 一、安装Neo4j 参考文章&#xff1a;neo4j下载安装配置步骤-CSDN博客 二、Neovis使用 1.npm引入 ?npm ins…

《宇宙机器人》提示错误弹窗“找不到d3dx9_43.dll”是什么原因?“d3dx9_43.dll缺失”怎么解决?

电脑游戏运行时常见问题解析&#xff1a;《宇宙机器人》提示“找不到d3dx9_43.dll”的解决之道 TGA2024落幕&#xff0c;年度最佳游戏——《宇宙机器人》&#xff0c;作为一名在软件开发领域深耕多年的从业者&#xff0c;我深知电脑游戏在运行过程中可能会遇到的各种挑战&…

Cesium 限制相机倾斜角(pitch)滑动范围

1.效果 2.思路 在项目开发的时候&#xff0c;有一个需求是限制相机倾斜角&#xff0c;也就是鼠标中键调整视图俯角时&#xff0c;不能过大&#xff0c;一般 pitch 角度范围在 0 至 -90之间&#xff0c;-90刚好为正俯视。 在网上查阅了很多资料&#xff0c;发现并没有一个合适的…

28. Three.js案例-创建圆角矩形并进行拉伸

28. Three.js案例-创建圆角矩形并进行拉伸 实现效果 知识点 WebGLRenderer (WebGL渲染器) WebGLRenderer 是 Three.js 中用于渲染 3D 场景的主要渲染器。 构造器 WebGLRenderer( parameters : Object ) 参数类型描述parametersObject渲染器的配置参数&#xff0c;可选。 …

transformer学习笔记-自注意力机制(2)

经过上一篇transformer学习笔记-自注意力机制&#xff08;1&#xff09;原理学习&#xff0c;这一篇对其中的几个关键知识点代码演示&#xff1a; 1、整体qkv注意力计算 先来个最简单未经变换的QKV处理&#xff1a; import torch Q torch.tensor([[3.0, 3.0,0.0],[0.5, 4…

基于米尔全志T527开发板的OpenCV进行手势识别方案

本文将介绍基于米尔电子MYD-LT527开发板&#xff08;米尔基于全志T527开发板&#xff09;的OpenCV手势识别方案测试。 摘自优秀创作者-小火苗 米尔基于全志T527开发板 一、软件环境安装 1.安装OpenCV sudo apt-get install libopencv-dev python3-opencv 2.安装pip sudo apt…