数据结构——排序大汇总(建议收藏)

news2024/9/25 16:28:57

这篇文章将为大家详细讲解各大排序的基本思想与实现代码~

内有动图

首先,我们来看常见的排序有以下几大类:

1.插入排序

插入排序的主要思想是将每个位置的元素插入到前面已具备顺序的数组中

实际中我们玩扑克牌时,就用了插入排序的思想  

 

1.1动图展示

 

1.2代码实现

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

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定  

2.希尔排序

希尔排序法又称缩小增量法

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

2.1动图展示

2.2代码实现

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap!=1) {
		gap=gap/3+1;
		for (int i = 0; i < gap; i++)
		{
			for (int j = 0; j < n - gap-1; j += gap)//或者j++变为三重循环
			{
				int end = j;
				int tmp = a[end + gap];
				while (end >= 0)
				{
					if (tmp < a[end])
					{
						a[end + gap] = a[end];
					}
					else {
						break;
					}
					end-=gap;
				}
				a[end + gap] = tmp;
			}
		}
	}
	
}

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定,因为咱们的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:O(n^1.25) 到 O(1.6*n^1.25)来算。

3.选择排序

直接选择排序:

  1. 在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素
  2. 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
  3. 在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

3.1动图展示

 

3.2代码实现

// 选择排序
void SelectSort(int* a, int n)
{
	int max;
	int min;

	int end = n - 1;
	int head = 0;
	while (end > head)
	{
		max = min = head;
		for (int i = head; i <= end; i++)
		{
			if (a[i] < a[min])
			{
				min = i;
			}
			if (a[i] > a[max])
			{
				max = i;
			}
		}
		if (max == head && min == end)
		{
			Swap(&a[max], &a[end]);

		}
		else if (min == end)
		{

			Swap(&a[min], &a[head]);
			Swap(&a[max], &a[end]);

		}else
		{
			Swap(&a[max], &a[end]);

			Swap(&a[min], &a[head]);
		}
		
		end--;
		head++;
	}

}

上面的方法是选择排序的改进,一趟同时找出最大和最小数 

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定 

4.堆排序

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

4.1动图展示

 

4.2代码实现

// 堆排序
void AdjustDwon(int* a, int n, int root)
{
	//建大堆
	int parent = root;
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child+1<n &&a[child] < a[child + 1])//要有右孩子才能比较
		{
			child++;
		}
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
		}
		parent = child;
		child = parent * 2 + 1;
	}
}
void HeapSort(int* a, int n)
{
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDwon(a, n, i);
	}
	while (n--)
	{
		Swap(&a[0], &a[n]);
		AdjustDwon(a, n, 0);
	}
}

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定  

5.冒泡排序

交换排序基本思想:

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置

交换排序的特点是:

将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

5.1动图展示

 

5.2代码实现

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

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定  

6.快速排序

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

6.1 hoare版本

6.1.1动图展示

6.1.2代码实现

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{

	//三数取中
	int keyi = Getmid(a, left, right);
	Swap(&a[keyi], &a[left]);
	keyi = left;



	int begin = left;
	int 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]);
	}
	Swap(&a[keyi], &a[begin]);
	keyi = begin;
	return keyi;
}
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);
	//int keyi = PartSort2(a, left, right);
	//int keyi = PartSort3(a, left, right);



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

6.2 挖坑法

6.2.1动图展示

6.2.2代码实现

// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
	//三数取中
	int keyi = Getmid(a, left, right);
	Swap(&a[keyi], &a[left]);
	keyi = left;

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

	}
	a[pit] = key;
	return pit;

}
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);
	int keyi = PartSort2(a, left, right);
	//int keyi = PartSort3(a, left, right);



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

6.3 前后指针法

6.3.1动图展示

6.3.2代码实现

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
	//三数取中
	int keyi = Getmid(a, left, right);
	Swap(&a[keyi], &a[left]);
	keyi = left;

	int prev = left;
	int cur = prev+1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi]&&cur!=++prev)
		{
			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);
	//int keyi = PartSort2(a, left, right);
	int keyi = PartSort3(a, left, right);



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

6.4快速排序非递归实现 

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	Stack 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 = PartSort3(a, begin, end);

		if(end>keyi+1)
		{
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}
		if(begin<keyi-1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}
	}
	STDestroy(&st);
}

快速排序优化

  1. 三数取中法选key
  2. 递归到小的子区间时,可以考虑使用插入排序 

这两步在上述代码中都有所体现

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定 

7.归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

7.1动图展示

7.2代码实现

7.2.1递归实现

void _MergeSort(int* a, int* tmp, int begin, int end)
{
	//递归停止条件
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;

	//子问题
	_MergeSort(a, tmp, begin1, end1);
	_MergeSort(a, tmp, begin2, end2);

	//当前问题
	//升序
	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 fail");
		return;
	}
	_MergeSort(a, tmp, 0, n-1);
	free(tmp);
	tmp = NULL;
}

7.2.2非递归实现

// 归并排序非递归实现
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)
	{
		int i;
		for (i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i;
			int end1 = i + gap-1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap-1;
			int j = i;
			// 第二组都越界不存在,这一组就不需要归并
			if (begin2 >= n)
				break;

			// 第二的组begin2没越界,end2越界了,需要修正一下,继续归并
			if (end2 >= n)
				end2 = n - 1;
			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, tmp, n * sizeof(int));
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

归并排序的特性总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定 

8.计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 

操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中   

8.1动图展示

 

8.2代码实现

void CountSort(int* a, int n)
{
	int max;
	int min;
	max = min = a[0];
	for (int i = 1; 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*)calloc(range, sizeof(int));
	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;
		}
	}
	free(count);
	count = NULL;
}

计数排序的特性总结:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(范围)

9.排序算法复杂度计稳定性总结 

 

 注意:这里的稳定性是指:指数组中相同元素在排序后相对位置不发生变化。

也就是原先一样大的数在排序后的相对位置不改变 

---------------------------------------------------------------------------------------------------------------------------------

本次讲解到此结束,喜欢的小伙伴们记得点赞收藏哦~ 

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

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

相关文章

Adobe正通过数字体验改变世界

在当今这个数字化飞速发展的时代&#xff0c;Adobe公司正以其创新的技术和卓越的产品引领着创意设计领域的变革。从Adobe发布的生成式AI工具&#xff08;Adobe Firefly&#xff09;&#xff0c;到Illustrator和Photoshop的新AI功能&#xff0c;再到广受认可的Adobe国际认证&…

【Golang 面试基础题】每日 5 题(七)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

opencv入门(四)

文章目录 一、形态学转换1.1 图像腐蚀1.1.1 erode():用于实现对图像的腐蚀操作1.2 图像膨胀1.2.1 dilate():实现对图像的膨胀操作1.3 图像 开\闭运算、梯度运算、顶帽运算、底帽运算1.3.1 morphologyEx():实现对图像的 开\闭运算、梯度运算、顶帽运算、底帽运算一、形态学转…

DATEDIFF()- Date Functions-SQL函数

DATEDIFF&#xff08;&#xff09;- Date Functions DATEDIFF() 函数是一种用于计算日期差异的常见日期函数。 通常用于比较两个日期之间的时间跨度&#xff0c;以便进行日期计算和分析。 语法 大多数数据库中&#xff0c;DATEDIFF() 函数的语法&#xff1a; DATEDIFF(unit,…

C++ | Leetcode C++题解之第278题第一个错误的版本

题目&#xff1a; 题解&#xff1a; class Solution { public:int firstBadVersion(int n) {int left 1, right n;while (left < right) { // 循环直至区间左右端点相同int mid left (right - left) / 2; // 防止计算时溢出if (isBadVersion(mid)) {right mid; // 答案…

MySQL练习05

题目 步骤 触发器 use mydb16_trigger; #使用数据库create table goods( gid char(8) primary key, name varchar(10), price decimal(8,2), num int);create table orders( oid int primary key auto_increment, gid char(10) not null, name varchar(10), price decima…

Pytorch使用教学6-张量的分割与合并

在使用PyTorch时&#xff0c;对张量的分割与合并是不可避免的操作&#xff0c;本节就带大家深刻理解张量的分割与合并。 在开始之前&#xff0c;我们先对张量的维度进行深入理解&#xff1a; t2 torch.zeros((3, 4)) # tensor([[0., 0., 0., 0.], # [0., 0., 0., 0.…

【PyTorch】基于YOLO的多目标检测项目(二)

【PyTorch】基于YOLO的多目标检测项目&#xff08;一&#xff09; 【PyTorch】基于YOLO的多目标检测项目&#xff08;二&#xff09; YOLO-v3网络由跨距为2的卷积层、跳跃连接层和上采样层组成&#xff0c;没有池化层。网络接收一幅416 * 416的图像作为输入&#xff0c;并提供三…

华为网络模拟器eNSP安装部署教程

eNSP是图形化网络仿真平台&#xff0c;该平台通过对真实网络设备的仿真模拟&#xff0c;帮助广大ICT从业者和客户快速熟悉华为数通系列产品&#xff0c;了解并掌握相关产品的操作和配置、提升对企业ICT网络的规划、建设、运维能力&#xff0c;从而帮助企业构建更高效&#xff0…

数据结构 | LinkedList与链表

前言 ArrayList底层使用连续的空间,任意位置(尤其是0位置下标)插入或删除元素时,需要将该位置后序元素 整体 往前或往后搬移,故时间复杂度为O(N). 优点(给定一个下标,可以快速查找到对应的元素,时间复杂度为O(1))增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗.增容一…

Linux进程——环境变量之二

文章目录 环境变量查看环境变量获取环境变量main()的第三个参数本地变量全局环境变量内建命令与常规命令 环境变量 查看环境变量 在上一篇文章中我们只说了查看某个环境变量的值&#xff0c;那么如何查看所有的环境变量呢 使用指令env即可 例如 这里我们也不需要全部记住&a…

FastAPI(七十四)实战开发《在线课程学习系统》接口开发-- 删除留言

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 之前文章FastAPI&#xff08;七十三&#xff09;实战开发《在线课程学习系统》接口开发-- 回复留言&#xff0c;那么我们这次分享删除留言接口的开发…

从0开始的STM32HAL库学习9

定时器输入捕获测频率 生成待测信号 配置环境 选择如上图所示 代码修改 在main函数中加入 HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); 测量信号频率 配置环境 如图所示打开TIM3定时器 1. 设置TI1FP1为ResetMode,即清空计数 2. 使用内部时钟 3. 通道 1 设置为输…

手机怎么设置不同的ip地址

在数字化日益深入的今天&#xff0c;智能手机已成为我们生活、工作和学习中不可或缺的设备。然而&#xff0c;随着网络应用的广泛和深入&#xff0c;我们有时需要为手机设置不同的IP地址来满足特定需求。比如&#xff0c;避免网络限制、提高网络安全、或者进行网络测试等。本文…

tarojs项目启动篇

TaroJS 是一个开放式跨端开发解决方案&#xff0c;使用 React 语法规范来开发多端应用&#xff08;包括小程序、H5、React Native 等&#xff09;。它可以帮助开发者高效地构建出在不同端上运行一致的应用。以下是启动 TaroJS 项目&#xff08;本来就有的旧项目&#xff09;的步…

经典文献阅读之--LIV-GaussMap(实时3D辐射场地图渲染的LiDAR惯性视觉融合算法)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

一文总结代理:代理模式、代理服务器

概述 代理在计算机编程领域&#xff0c;是一个很通用的概念&#xff0c;包括&#xff1a;代理设计模式&#xff0c;代理服务器等。 代理类持有具体实现类的实例&#xff0c;将在代理类上的操作转化为实例上方法的调用。为某个对象提供一个代理&#xff0c;以控制对这个对象的…

Haproxy 下载、编译部署、使用

文章目录 前言Haproxy 下载、编译部署、使用1. 下载2. 编译部署3. 使用3.1. 验证配置文件3.2. 启动 HAProxy 并指定配置文件路径3.3. 关闭HAProxy3.4. 重载HAProxy 3. 测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&am…

go-kratos 学习笔记(7) 服务发现服务间通信grpc调用

服务发现 Registry 接口分为两个&#xff0c;Registrar 为实例注册和反注册&#xff0c;Discovery 为服务实例列表获取 创建一个 Discoverer 服务间的通信使用的grpc&#xff0c;放到data层&#xff0c;实现的是从uses服务调用orders服务 app/users/internal/data.go 加入 New…

可见性::

目录 定义&#xff1a; 解决方法&#xff1a; ①使用synchronized实现缓存和内存的同步 修改一&#xff1a; 加入语句&#xff1a; 代码&#xff1a; 修改2&#xff1a; 在代码块中加入&#xff1a; 代码&#xff1a; 执行结果&#xff1a; 原因&#xff1a; ②使用…