数据结构——排序算法

news2024/9/30 13:19:26

1、排序的概念

        排序是指的是将一组数据(如数字、单词、记录等)按照某种特定的顺序(升序或降序)进行排列的过程。排序算法是实现排序的程序或方法,它们在软件开发和数据处理中扮演着至关重要的角色。

排序算法可以根据不同的标准进行分类,如:

  1. 内部排序外部排序:内部排序是指数据全部加载到内存中进行排序,而外部排序则涉及处理大量数据,这些数据可能太大而无法一次性放入内存。

  2. 比较排序非比较排序:比较排序是基于比较操作来决定元素之间的相对顺序,如快速排序、归并排序等。非比较排序不通过比较来确定元素的顺序,如计数排序、基数排序等。

  3. 稳定性:稳定的排序算法能够保持相等元素的原始顺序,而非稳定排序算法可能会改变相等元素的顺序。

  4. 时间复杂度:算法执行所需的时间与输入数据量的关系。常见的时间复杂度有O(n)、O(n log n)、O(n^2)等。

  5. 空间复杂度:算法执行过程中需要的额外空间量。

  6. 在线排序离线排序:在线排序是指数据一个接一个地处理,而离线排序则是在所有数据都可用的情况下进行。

2、常见的排序算法

 接下来我们一一实现:

直接插入排序

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

        排序思想是认为第一个元素是有序的,然后从第二个元素开始排序,先把元素保存到tem中,然后从后往前遍历数组,找到比tem大的就向后挪一位,遍历完后把tem放到下标为ennd+1的位置上,继续排下一个数 

 看一下动图演示:

直接插入排序的

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

空间复杂度:O(1) 

稳定性:稳定

 希尔排序

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

	}
}

        希尔排序(Shell Sort)是插入排序的一种更高效的改进版本,由Donald Shell于1959年提出。它是基于插入排序的一种分组比较排序算法,也被称为“缩小增量排序”。希尔排序的核心思想是将原始数据集分割成若干个子序列进行插入排序,这些子序列由原始数据集中的元素按照增量序列划分而来。随着增量序列的减小,整个数据集逐渐被整合成一个有序的序列。

动图演示:

  1. 选择增量序列:增量序列的选择对希尔排序的性能有很大影响。常见的增量序列有希尔原始序列(N/2,N/4,...,1),Hibbard增量序列(1, 3, 7, ..., 2^k - 1),Knuth序列(1, 4, 13, ..., (3^k - 1)/2)等。

  2. 分组:按照当前增量将数组分为若干子序列。例如,如果当前增量是5,那么索引为0, 5, 10, ...的元素将分为一组,索引为1, 6, 11, ...的元素将分为另一组,以此类推。

  3. 对各组进行插入排序:在每个子序列上应用插入排序算法,使得每个子序列有序。

  4. 减小增量,重复上述步骤:随着增量的减小,子序列的间隔逐渐减小,最终当增量为1时,整个数组将作为一个序列进行一次插入排序,此时数组应该是基本有序的,所以最后一步的插入排序会非常快。

  5. 完成排序:当增量减小到1时,整个数组将作为一个序列进行最后一次插入排序,此时数组已经基本有序,所以排序会很快完成。

时间复杂度:希尔排序的时间复杂度依赖于增量序列的选择。最坏情况下的时间复杂度为O(n^2),但是对于中等大小的数组,实际运行时间可能接近于O(n log^2 n)。最佳情况下,如果增量序列选择得当,时间复杂度可以达到O(n log n)。 有人算出希尔排序的时间复杂度接近于O(N^1.3),我们记住就行了。

空间复杂度:O(1) 

稳定性:不稳定

选择排序

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++)
		{
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
		}
		swap(&a[begin], &a[mini]);
		if (maxi == begin)
			maxi = mini;
		
		swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}

 选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理是每次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

        上面是对选择排序的优化,同时选取最大和最小的值进行比较,然后放在队头和队尾

         if语句是为了当前数组中最大的数在最小的位置上,会发生重复交换,如果加个if语句可以避免这种情况,如果不理解的小伙伴可以把if语句去掉,调试一下去发现问题,肯定会理解的更加透彻!

冒泡排序

代码展示:

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

        冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

冒泡排序的基本思想是:通过相邻元素之间的比较和交换,每一轮比较后,最大的元素会“冒泡”到数列的最后位置。这个过程会重复进行,直到整个数列有序。

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

空间复杂度:O(1)

稳定性:稳定

 

堆排序

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (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)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	while (end >= 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

         堆排序使用了二叉树的思想,用向下调整建堆,如果我们建的是小堆,然后交换堆顶和堆最后一个元素,这样最小的元素就到了最后,然后依次向下调整,就形成了升序,

快速排序

        快速排序(Quick Sort)是一种高效的排序算法,由C. A. R. Hoare在1960年提出。它的基本思想是通过一趟排序将待排记录分隔成独立的两部分,其中一部分的所有记录的关键字均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。快速排序的核心是分治法(Divide and Conquer)策略。

void QuickSortHoare(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int keyi = left;
	while (left < right)
	{
		while (left<right && a[right]>=a[keyi])
			right--;
		while (left < right && a[left] <= a[keyi])
			left++;
		swap(&a[left], &a[right]);
	}
	swap(&a[keyi], &a[left]);
	keyi = left;
	QuickSortHoare(a, begin, keyi - 1);
	QuickSortHoare(a, keyi+1, end);
}

快速排序还有改进的双指针法:

//快速排序  双指针版本
void QuickSortTowPorters(int* a, int left, int right)
{
	if (left >= right)
		return;
	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
			swap(&a[prev], &a[cur]);
		cur++;
	}
	swap(&a[keyi], &a[prev]);
	keyi = prev;
	QuickSortTowPorters(a, left, keyi - 1);
	QuickSortTowPorters(a, keyi+1, right);
}

 这段代码是比较精简的,请看动图演示

归并排序 

//归并排序
void _mergeSort(int* a, int begin, int end, int* tem)
{
	if (begin == end)
		return;
	int mid = (begin + end) / 2;
	_mergeSort(a, begin, mid, tem);
	_mergeSort(a, mid + 1, end, tem);
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
			tem[i++] = a[begin1++];
		else
			tem[i++] = a[begin2++];
	}
	while (begin1 <= end1)
	{
		tem[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tem[i++] = a[begin2++];
	}
	memcpy(a + begin, tem + begin, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* a, int n)
{
	int* tem = (int*)malloc(sizeof(int) * n);
	assert(tem);
	_mergeSort(a, 0, n - 1, tem);
	free(tem);
	tem = NULL;
}

        归并排序(Merge Sort)是一种分治算法,其核心思想是将一个大问题分解成若干个较小的子问题来解决,然后将子问题的解合并起来得到原问题的解。在排序领域,归并排序通过不断地将数组分成两半,对每一半进行排序,然后将排序好的两半合并在一起,从而达到整个数组有序的目的。

归并排序的步骤可以分为两个主要部分:分裂(Divide)和合并(Merge)。

  1. 分裂(Divide)

    • 将数组从中间分成两个相等(或接近相等)的子数组。
    • 对这两个子数组分别进行归并排序。
    • 这个过程会递归进行,直到子数组的长度为1,此时子数组自然是有序的。
  2. 合并(Merge)

    • 将两个有序的子数组合并成一个更大的有序数组。
    • 合并过程中,需要一个临时数组来存放合并后的有序数组。
    • 比较两个子数组的前端元素,将较小的元素放入临时数组,然后移动对应的指针。
    • 重复这个过程,直到所有元素都被合并到临时数组中。
    • 将临时数组的内容复制回原数组(或者直接使用临时数组作为结果)。

         以下是归并排序的非递归实现,比较难理解

//归并排序  非递归实现
void MergeSortNonR(int* a, int n)
{
	int* tem = (int*)malloc(sizeof(int) * n);
	assert(tem);
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			//重点,难点
			int begin1 = j, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			//处理越界问题
			if (begin1 > n || begin2 > n)
				break;
			if (end2 > n)
				end2 = n - 1;

			int i = j;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
					tem[i++] = a[begin1++];
				else
					tem[i++] = a[begin2++];
			}
			while (begin1 <= end1)
			{
				tem[i++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tem[i++] = a[begin2++];
			}
			memcpy(a + j, tem + j, sizeof(int) *(end2-j+1));
		}
	}
}

 可以看一下动图展示:

        归并排序的总时间复杂度是O(n log n)。这意味着归并排序在最好、最坏和平均情况下都有相同的时间复杂度,即O(n log n)。这使得归并排序在处理大数据集时非常可靠和高效。

        需要注意的是,归并排序的空间复杂度也是O(n),因为它需要一个与输入数组大小相同的临时数组来存储合并的结果。在实际应用中,归并排序的常数因子较大,因此在小数组上的排序性能可能不如其他简单排序算法,如插入排序。然而,对于大型数据集,归并排序的优势就非常明显了。

        制作不易,希望对你有帮助,大家一起加油!

 

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

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

相关文章

Spring Boot 使用过滤器、拦截器、监听器

前言 作用 过滤器&#xff08;Filter&#xff09;&#xff1a;当有一堆请求&#xff0c;只希望符合预期的请求进来。拦截器&#xff08;Interceptor&#xff09;&#xff1a;想要干涉预期的请求。监听器&#xff08;Listener&#xff09;&#xff1a;想要监听这些请求具体做了…

【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《C语言》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、红黑树&#xff08;改造版&#xff09;1.1 结点1.2 迭代器1.2.1 operator1.2.2 operator- - 1.3 本体1.…

Stable Diffusion XL之使用Stable Diffusion XL训练自己的AI绘画模型

文章目录 一 SDXL训练基本步骤二 从0到1上手使用Stable Diffusion XL训练自己的AI绘画模型2.1 配置训练环境与训练文件2.2 SDXL训练数据集制作(1) 数据筛选与清洗(2) 使用BLIP自动标注caption(3) 使用Waifu Diffusion 1.4自动标注tag(4) 补充标注特殊tag(5) 训练数据预处理(标注…

什么是物联网监控平台?部署物联网平台有什么作用?

随着物联网技术的飞速发展&#xff0c;越来越多的企业开始关注并投入到这一领域。物联网监控平台作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐成为企业数字化转型的关键组件。今天&#xff0c;我们将深入探讨物联网监控平台的定义、部署物联网平台的作用&#xff0c;以…

企微获客助手功能,行为触发如何实现回传的?

获客助手&#xff0c;这个听起来就相当酷炫的名字&#xff0c;它实际上是一个帮助企业将推广流量快速导入企业微信的神器。通过它&#xff0c;企业可以吸引越来越多的用户加为好友&#xff0c;从而建立起更紧密的客户关系。但是&#xff0c;如何进一步提升导入企业微信的流量质…

Docker数据卷挂载

一、容器与数据耦合的问题: 数据卷是虚拟的&#xff0c;不真实存在的&#xff0c;它指向文件中的文件夹 &#xff0c;属主机文件系统通过数据卷和容器数据进行联系&#xff0c;你改变我也改变。 解决办法&#xff1a; 对宿主机文件系统内的文件进行修改&#xff0c;会立刻反应…

信号处理--情绪分类数据集DEAP预处理(python版)

关于 DEAP数据集是一个常用的情绪分类公共数据&#xff0c;在日常研究中经常被使用到。如何合理地预处理DEAP数据集&#xff0c;对于后端任务的成功与否&#xff0c;非常重要。本文主要介绍DEAP数据集的预处理流程。 工具 图片来源&#xff1a;DEAP: A Dataset for Emotion A…

docker关闭全部运行容器命令是什么?

环境&#xff1a; docker v22.1 问题描述&#xff1a; docker关闭全部运行容器命令是什么&#xff1f; 解决方案&#xff1a; 要关闭所有正在运行的Docker容器&#xff0c;可以使用如下命令&#xff1a; docker stop $(docker ps -a -q)这条命令首先执行 docker ps -a -q…

WiFiSpoof for Mac wifi地址修改工具

WiFiSpoof for Mac&#xff0c;一款专为Mac用户打造的网络隐私守护神器&#xff0c;让您在畅游互联网的同时&#xff0c;轻松保护个人信息安全。 软件下载&#xff1a;WiFiSpoof for Mac下载 在这个信息爆炸的时代&#xff0c;网络安全问题日益凸显。WiFiSpoof通过伪装MAC地址&…

【QQ版】QQ群短剧机器人源码 全网短剧机器人插件

内容目录 一、详细介绍二、效果展示2.效果图展示 三、学习资料下载 一、详细介绍 QQ版本可以兼容两个框架&#xff08;HTQQ&#xff0c;MYQQ这两个的vip版也可以使用) 支持私聊与群聊&#xff0c;命令是 搜剧影视关键词 如果无法搜索到影视资源&#xff0c;请使用下方命令&…

网络链路层之(2)PPP协议

网络链路层之(2)PPP协议 Author: Once Day Date: 2024年3月27日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSDN…

[TS面试]keyof和typeof关键字作用?

keyof和typeof关键字作用? keyof 索引类型查询操作符, 获取索引类型属性名, 构成联合类型 typeof 获取一个变量或者对象的类型 let str:string ‘hello’ // typeof str >:string keyof typeof 获取 联合类型的key enum A{A, B, C }type unionType keyof typeof A; /…

JDK8的下载安装与环境变量配置教程

前言 官网下载&#xff1a;Java Archive Downloads - Java SE 8u211 and later 现在应该没人用32位的系统了吧&#xff0c;直接下载Windows x64 Installer jdk-8u391-windows-x64.exe 一、安装JDK 1. 打开jdk-8u391-windows-x64.exe 2. 直接下一步 3. 这个地方不要动他&…

python opencv稍基础初学

傅里叶变换 傅里叶变换f​​​​​傅里叶分析之掐死教程&#xff08;完整版&#xff09;更新于2014.06.06 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/19763358 相当nice 傅里叶变换的作用 高频&#xff1a;变化剧烈的灰度分量&#xff0c;例如边界 低频&#xff1a;变…

P8649 [蓝桥杯 2017 省 B] k 倍区间:做题笔记

目录 思路 代码思路 代码 推荐 P8649 [蓝桥杯 2017 省 B] k 倍区间 思路 额嗯&#xff0c;这道题我刚上来是想到了前缀和&#xff0c;但是还要判断每个子序列&#xff0c;我就两层for嵌套&#xff0c;暴力解了题。就是我知道暴力肯定过不了但是写不出来其他的[留下了苦…

[linux] AttributeError: module ‘transformer_engine‘ has no attribute ‘pytorch‘

[BUG] AttributeError: module transformer_engine has no attribute pytorch Issue #696 NVIDIA/Megatron-LM GitHub 其中这个答案并没有解决我的问题&#xff1a; import flash_attn_2_cuda as flash_attn_cuda Traceback (most recent call last): File "<stdi…

golang 在多线程中避免 CPU 指令重排

发布日期&#xff1a;2024-03-26 16:29:39 起因 golang 的发明初衷便是多线程&#xff0c;是一门专门用于多线程高并发的编程语言。其独创的 GMP 模型在多线程的开发上提供了很大的便利。 现代计算机基本上都是多核 CPU 的结构。CPU 在进行指令运行的时候&#xff0c;为了提高…

基于大语言模型的云故障根因分析|顶会EuroSys24论文

*马明华 微软主管研究员 2021年CCF国际AIOps挑战赛程序委员会主席&#xff08;第四届&#xff09; 2021年博士毕业于清华大学&#xff0c;2020年在佐治亚理工学院做访问学者。主要研究方向是智能运维&#xff08;AIOps&#xff09;、软件可靠性。近年来在ICSE、FSE、ATC、EuroS…

【系统架构师】-第13章-层次式架构设计

层次式体系结构设计是将系统组成一个层次结构&#xff0c;每一层 为上层服务 &#xff0c;并作为下层客户。 在一些层次系统中&#xff0c;除了一些精心挑选的输出函数外&#xff0c; 内部的层接口只对相邻的层可见 。 连接件通过决定层间如何交互的协议来定义&#xff0c;拓扑…

免费SSL证书和付费SSL证书的区别点

背景&#xff1a; 在了解免费SSL证书和付费SSL证书的区别之前&#xff0c;先带大家了解一下SSL证书的概念和作用。 SSL证书的概念&#xff1a; SSL证书就是基于http超文本传输协议的延伸&#xff0c;在http访问的基础上增加了一个文本传输加密的协议&#xff0c;由于http是明…