七大排序的时间复杂度和空间复杂度

news2024/12/26 13:59:09

文章目录

  • 七大排序的分类
  • 时间复杂度
  • 空间复杂度
  • 稳定性
  • 直接插入排序
  • 希尔排序
  • 选择排序
  • 堆排序
  • 冒泡排序
  • 快速排序
  • 归并排序
  • 总结

七大排序的分类

在这里插入图片描述

时间复杂度

时间复杂度是指一个程序中基本语句被执行的次数,一般认为是最坏情况。

空间复杂度

空间复杂度是指在一个程序执行时要额外开辟的空间大小。

稳定性

什么是稳定性呢?这里的稳定性不是指时间复杂度稳定,而是原数据中相同值的前后位置是否会发生改变。
例如 1 2 1 4 5如果在排序的过程中第二个1跑到了第一个1的前面,那么我们就说这个排序不稳定,记住是相同值的前后位置。当然是指有能力做到稳定我们就说它稳定,如果想让一个排序不稳定我们都能做到让它不稳定。

直接插入排序

直接插入排序就是从第i个数开始依次向前i-1个有序数中插入。

//插入排序
void InsertSort(int* a, int n)
{
	assert(a);
	//将无序数组排成有序
	//从第一个值开始向前比较,前n个值有序再让第n+1个值插入,前面的有序数组
	for (int end = 1; end < n; end++)
	{
		//单趟排序 将一个值插入一个有序数组
		//值会被覆盖,所以要提前保存
		int x = a[end];
		int j;
		for (j = end - 1; j >= 0; j--)
		{
			//此时n是最后一个元素的下标
			if (x < a[j])
			{
				a[j + 1] = a[j];
			}
			else
			{
				break;
			}
		}
		a[j + 1] = x;
	}
}

最好情况时,数组是有序的,时间复杂度为O(n)
最坏情况时,数组是逆序的,时间复杂读为1/2 * n * (n + 1),所以对应的也就是O(n2)
所以直接插入的时间复杂度为O(n)~O(n2)
空间复杂度:O(1)
稳定性:稳定

希尔排序

希尔排序就是把数据进行分组,然后对每组数据进行排序,每组的数据个数逐渐增加,被分成的组数也就逐渐变少。
在这里插入图片描述

// gap > 1 预排序
// gap == 1 直接插入排序
void ShellSort(int* a, int n)
{
	//思想:分组排序,让数据尽量有序,接着让间距gap逐渐减小到1
	//gap例如 gap = 2时,1,3,2,4,5----1,2,5是一组,3,,4是一组
	int gap = n;
	int end = 0;
	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];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

当然这里的gap也可以是n/3
至于时间复杂度:
希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就
    会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的
    希尔排序的时间复杂度都不固定:
    《数据结构(C语言版)》— 严蔚敏
    在这里插入图片描述

空间复杂度这里我们就很容易计算,因为都是在原数组上进行操作的,所以空间复杂度为O(1)
稳定性:不稳定

选择排序

选择排序:从第当前位置开始依次向后遍历找到最大或最小(降序或升序)然后和它交换。
我们也可以同时记录两个位置找到最大和最小。在升序中让最小和第i位置交换,让最大和第n-1-i位置交换,此外还要考虑到一种特殊情况第i位置就是最大值时,在找到最小后它和下标为min的最小值交换了,所以此时第n-1-i位置就要和min位置的值交换了。
在这里插入图片描述

//方法二
void SelectSort(int* a, int n)
{
	int left, right;
	int mini, maxi;

	left = 0;
	right = n - 1;
	while (left < right)
	{
		
		mini = left;
		maxi = left;
		for (int i = left + 1; i <= right; i++)
		{
			//找出最大和最小值的下标
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		Swap(&a[left], &a[mini]);
		//要考虑最初定义的mini是不是max,如果是那么在上面的交换后,它就被换走了。
		if (maxi == left)
		{
			Swap(&a[right], &a[mini]);
		}
		else
		{
			Swap(&a[right], &a[maxi]);
		}
		left++;
		right--;
	}
}

这里是同时找最大和最小,单项选择和双向选择的区别就是,双向选择可以少走一些选择的过程,但是对应的时间复杂度并没有发生改变都是O(n2)。
单向:1/2*(n-1 + 1)* 对应的量级就是O(n2)
双向:大致 1/2*(n-1 + 2)n1/2 因为它少走了一般的路程所以要除以2,但是量级没有改变,仍是O(n2)
直接选择排序与原数据顺序无关。
空间复杂度:O(1)
稳定性:不稳定

堆排序

堆排序,要先进行建堆,大根堆或者小根堆,再根据topk问题原理将topk与最后一个值交换,所以此时最后一个值也就在第一个位置,再让它进行向下调整。
向上建堆的过程时间复杂度为:最差为n*logn-n
调整的时间复杂度:
在这里插入图片描述

一共有log2n层,从第1层开始向下调整,调整次数由n到1,调整的数据个数也由2^(n-1) 到 2^1个
然后利用错位相减法得到的结果是log2n * h * 2^(h - 1),其中h * 2^(h - 1)也就数据的总个数n,所以就可大致得出时间复杂度为O(n*log2n),堆排序不会受原数据顺序的影响,因为都要从top开始向下调整到最后。
空间复杂度:都是在原数组上进行操作的,只有一些额外的临时变量,可归为O(1)
稳定性:不稳定 例如对大根堆排升序时,数组值顺序为2 2 1,第一次调完之后就变成了 2 1 2
注意这里粗体和斜体2的位置变化

冒泡排序

void BubbleSort(int* a, int n)
{
	assert(a);
	assert(n);
	for (int i = 0; i < n; i++)
	{
		bool exchange = false;
		for (int j = 1; j < n - i; j++)
		{
			if (a[j] < a[j - 1])
			{
				Swap(&a[j], &a[j - 1]);
				exchange = true;
			}
		}
		if (exchange == false)
		{
			break;
		}
	}
}

冒泡排序的时间复杂度:
冒泡排序的基本语句就是Swap语句
Swap执行次数由n次逐渐减少到1次。
所以执行总次数为[ n*(1+n)] / 2。所以时间复杂度为N^2;(计算复杂度时系数可以省略,粗略计算n+1近似于n)
当原数据本来就有序时,只需遍历一遍即可,判断一下数据有么有发生交换,因为有序所以没有交换,就可直接跳出,此时的时间复杂度为O(N)。
所以综上所述冒泡排序与原数据有关,时间复杂度为O(N)~O(N^2)。
冒泡排序的空间复杂度:
这里的数组空间是已经开辟好的,不属于在这个排序空新开辟的,所以不计算在内。而这里只有一些i,j等变量属于新开辟的,且它们只开辟一次空间就够了,后面会重复利用,即所有的i,j都共用一个存储空间, 所以他们是属与O(1)这个量级的,即它的空间复杂度为0(1);
稳定性:稳定

快速排序

在这里插入图片描述

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	//设一个数作为基准,我们一般选择最左边或者是最右边的数作为基准,
	//最终结果是基准的左右两边分别大于和小于它或小于大于它,具体看是排升序还是降序
	//我们以排升序举例
	//int keyi = left;
	//如果数据已经接近有序,那么直接选取某一个数作为基准很有可能会导致栈溢出,
	int midi = GetMidIndix(a, left, right);
	//让选择的基准和最左边的值换位置
	Swap(&a[midi], &a[left]);
	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]);
	return left;//返回a[keyi]现在所在的下标,就是为了找到将数据一分为二的下标,一边比它大一边比他小
}

快速排序也与原数据有关,是否接近有序,它的最好情况时时间复杂度为nlogn
如上图递归深度为logn,每一层比较的数都为n,最后几层可能会不为n,但是也接近,所以比较执行的总次数为n
logn。
当它为逆序时最坏,时间复杂度为n2,因为逆序所以它递归的深度为n,每一层比较的数都为n,最后几层可能会不为n,但是也接近,所以比较执行的总次数为N^2。
综上所述,快速排序最好情况时,时间复杂度为n*logn,最坏情况时复杂度为n^2。
空间复杂度:最好时递归深度为logn,最坏时递归深度为n,每次开辟的空间是为基准keyi开辟的,里面存放的就是基准下标,最好情况时要选logn次基准,最坏情况时要选n次基准,所以快速排序的空间复杂度为O(logn)~O(n)。

归并排序

我们这里说的归并排序是二路归并。
把每个元素都分开,然后两两合直到合并到没有元素,再合并的过程中进行排序。
在这里插入图片描述

//递归实现
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	assert(a);
	if (begin  >= end)//有大于的情况吗?
	{
		return;
	}
	//归并排序实质上对应于后序遍历
	// 求中位数是两边之和除以二
	//int mid = (end - begin - 1) / 2;
	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	//注意每次递归中的i等于begin
	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++];	
	}
	//最后别忘了把数据拷贝到原数组
	//注意拷贝的源位置和目标位置,要加上begin
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

归并排序应用的是标准的二分法,最好和最坏情况时间复杂度都是nlog2n,解释:一共需要分log2n层,每一层都有n个数据要进行比较,且与原数据顺序无关。
空间复杂度:O(n)因为创建了一个临时数组tmp,且每次递归调用都是用的同一个临时数组,并不是开辟了n
log2n个临时空间。
稳定性:稳定

总结

在这里插入图片描述

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

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

相关文章

美团DAT:A Dual Augmented Two-tower Model for Online Large-scale Recommendation

A Dual Augmented Two-tower Model for Online Large-scale Recommendation 美团的对偶增强双塔为了user塔和item塔操碎了心&#xff0c;众所周知&#xff0c;双塔的一个大毛病就是item和user的交叉太晚&#xff0c;重要的信息经过层层神经网络的抽象提取&#xff0c;有些重要…

15、异常处理

文章目录 1、错误处理1、默认规则2、定制错误处理逻辑3、异常处理自动配置原理4、异常处理步骤流程 【尚硅谷】SpringBoot2零基础入门教程-讲师&#xff1a;雷丰阳 笔记 路还在继续&#xff0c;梦还在期许 1、错误处理 1、默认规则 默认情况下&#xff0c;Spring Boot提供/er…

傻傻的分不清 Comparator 和 Comparable 接口? 两分钟弄懂~

目录 一、Comparable 接口 二、Comparator 接口 一、Comparable 接口 Comparable 是一个排序接口&#xff08;意味着该支持排序&#xff09;&#xff0c;可以看作内比较器&#xff0c;也就是说可以和自己比较&#xff0c;通常用来自己属性与自己属性进行比较&#xff0c;最后通…

百年孤独 -- 有感

《创世记》之后&#xff0c;首部值得全人类阅读的文学巨著。 — 纽约时报 加西亚 马尔克斯以小说作品创建了一个自己的世界&#xff0c;一个浓缩的宇宙&#xff0c;其中喧嚣纷乱却又生动可信的现实&#xff0c;映射了一篇大陆及其人民的富足与贫困。 — 诺贝尔文学奖颁奖辞 马…

wordpress+apache搭建问题总结

访问首页出现服务器目录 需要到apache的httpd.conf 添加index.php默认值 更新失败。 此响应不是合法的JSON响应 大多出现于修改固定连接后 打开httpd.conf文件,把AllowOverride None修改为AllowOverride All,重启apache即可解决 AllowOverride Noneyi意味着忽略.htaccess文件…

VS2022中创建C++SDK库(dll动态库)并调用SDK库(dll动态库)

VS2022中创建CSDK库(dll动态库)并调用SDK库(dll动态库) 一、说明 通过前两篇的文章我们知道了封装一个Qt下的SDK库&#xff08;dll动态链接库&#xff09;和封装Pimpl模式。 Qt创建SDK库(dll动态库)并调用SDK库(dll动态库) SDK(动态链接库dll)的封装技巧 本篇介绍在C VS下…

深度解析动态分配内存管理

目录 ​编辑 一. 前言 二.正文 2.0 计算机中的内存 2.1 动态申请函数头文件 2.2 malloc函数 2.3 free函数 2.3 calloc函数 2.4 realloc函数 2.5 经典笔试题 1. 2. 2.6 柔性数组 三.结语 一. 前言 本小节跟大家分享动态内存管理的知识&#xff0c;希望能给大家带…

2020年全国10m分辨率地表覆盖数据集

1.数据概况 地球观测技术的进步使得土地覆盖制图的空间分辨率不断提高&#xff0c;但这也导致了高分辨率影像样本收集和更新变得更加耗时。本研究提出了一种基于样本噪声学习的跨分辨率土地覆盖制图框架&#xff0c;利用现有低分辨率产品生成大量高分辨率影像标签用于深度语义…

【Python从入门到进阶】17、文件的读写操作

接上篇《16、文件的打开和关闭》 上一篇我们学习了Python打开和关闭文件的操作。本篇我们主要学习Python文件对象包含哪些方法&#xff0c;并学习文件如何进行读、写、定位和删除。 一、文件对象的属性和方法 通过上一篇的学习我们知道&#xff0c;open函数会返回给我们一个文…

一个简单的盒模型嵌套---一个大盒子里面嵌套两个中盒子,右边中盒子嵌套六个小盒子

html代码&#xff1a; <div class"box1"><div class"box2"></div><div class"box3"><div class"box4"></div><div class"box5"></div><div class"box6">&…

apple pencil值不值得购买?ipad平替电容笔安利

自从Apple Pencil上市后&#xff0c;平替电容笔在国内的销量&#xff0c;也是随之而增加&#xff0c;虽然Apple Pencil的性能不错&#xff0c;但由于其的价格太高&#xff0c;所以不少普通用户都买不起。现在市面上有很多能够代替苹果原装笔的平替电容笔。在这里&#xff0c;我…

Debezium系列之:自定义表级别Topic的名称

Debezium系列之:自定义表级别Topic的名称 一、需求背景二、去掉部分topic名称的方法三、debezium具体实现方法四、完整配置一、需求背景 debezium表级别Topic命名规则为:topic.prefix参数的值.数据库名称.表的名称现在支持实时需求,命名名称需要修改为topic.prefix参数的值.…

SAP KANBAN 从入门到放弃系列之生产补货模式

目录 系统操作 看板牌-生产补货 系统配置&#xff1a; 补充策略配置 看板状态配置 创建物料主数据 创建供应区域 创建控制周期-经典看板 系统操作 看板牌-生产补货 需要必要的看板配置和主数据设置 第一步&#xff1a;事务代码:PK13N&#xff0c;需求端KANBAN.供应端…

【数据库】 | 初始数据库

&#x1f397;️ 博客新人&#xff0c;希望大家一起加油进步 &#x1f397;️ 乾坤未定&#xff0c;你我皆黑马 目录 1、什么是数据库2、数据库分类3、 一些重要概念4、 数据存储 1、什么是数据库 存储数据用文件就可以了&#xff0c;为什么还要弄个数据库? 文件保存数据有以…

访京东查询(自定义查询,组件形式,多页面使用,自定义查询条件)

1、效果图 2、使用了Ant Design of Vue里面的tag属性&#xff0c;也可以不用自己写样式。 3、代码模块 1、创建一个公共组件vue文件 <template><a-card size"small" :bordered"false" style"margin-bottom: 10px; padding:0px;">&l…

SVG格式文件可以用什么软件打开?

SVG是一种基于矢量的图像文件格式&#xff0c;通过基于网格上的点和线的数学公式存储图像&#xff0c;与 JPEG 等基于像素的位图文件不同。这种文件类型可以在不损失任何质量的情况下生成很小的文件&#xff0c;因而加载速度也相对更快&#xff0c;也使其成为LOGO和复杂在线图形…

SQL——多表连接查询

若一个查询同时涉及两个或两个以上的表&#xff0c; 则称之为连接查询&#xff08;在FROM子句中体现)。 参与连接的表可有多个&#xff0c;但连接操作在两个表之间进行&#xff0c;即两两连接。 连接查询包括&#xff1a; 内连接 等值连接&#xff1a;用“”比较被连接列的列值…

场景提效:多维表格重塑项目管理流程

此模板适用场景&#xff1a;多任务多团队之间的项目管理 预计实现效果&#xff1a;一张万能表格汇总所有信息&#xff0c;并可以按多视图进行或查看&#xff0c;根据待办项自动定期提醒成员同步进度。 效果对比 需求旧方式新方式统一汇总管理信息全篇文字罗列&#xff0c;呈…

【Linux】线程

1.理解地址空间和页表 1.地址空间是进程能够看到的资源窗口 2.页表决定进程真正拥有的资源情况 3.合理的对地址空间和页表进行资源划分就可以对一个进程的所有资源进行划分&#xff1a;过地址空间分为栈区、堆区…通过页表映射到不同的物理内存。 在32位平台下&#xff0c;…

Mysql学习(十一) -- 常见问题处理

1. MySQL数据库cpu飙升的话你会如何分析 重点是定位问题。 使用top观察mysqld的cpu利用率 切换到常用的数据库使用show full processlist;查看会话观察是哪些sql消耗了资源&#xff0c;其中重点观察state指标定位到具体sql pidstat 定位到线程在PERFORMANCE_SCHEMA.THREADS中…