数据结构——排序

前言:哈喽小伙伴们好久不见,也是顺利的考完试迎来了寒假。众所周知,不怕同学是学霸,就怕学霸放寒假,假期身为弯道超车的最佳时间,我们定然是不能懒散的度过。

今天我们就一起来学习数据结构初阶的终章——七大排序

本文所有的排序演示都为升序排序


目录

一.为什么要排序

二.七大排序

1.冒泡排序

2.选择排序

3.堆排序

4.插入排序

5.希尔排序

6.快速排序

7.归并排序

三.总结


一.为什么要排序

我们知道,数据结构的诞生是为了存放和管理众多的数据。而排序,就是最常用也是必不可少的数据管理方式,能够帮助我们更加轻松和方便的去找到自己想要的结果。


二.七大排序

数据结构中常用的排序有七种:

  • 冒泡排序
  • 选择排序
  • 堆排序
  • 插入排序
  • 希尔排序
  • 快速排序
  • 归并排序

下面我们就来一一讲解这七大排序的排序方式及其性能优劣


1.冒泡排序

冒泡排序相信有很多小伙伴在学习C语言的时候就已经有所了解了,如下图所示,冒泡排序是从一组数据的第一个元素开始两个两个紧挨着进行比较,如果前者大于后者就进行交换,这样便可以使每排序一次就可以将最大值置于数据末尾

注意,这里紧挨着排序是指,1和2排完之后,2紧接着和3排

冒泡排序的原码如下: 

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

能够看出,冒泡排序需要两层循环控制,内层循环控制单趟排序,外层循环控制总排序次数

n表示数据个数,所以总共需要排序n-1次,即外层循环的次数,同样也是n个数据之间两两比较的次数

因为每经过一轮排序,我们都能排好最大值,所以每下一次排序都比上一次少一个数据,所以我们通过i < n - 1 - j 的方式就可以完美实现这一点。当然你也可以定义一个新变量去控制,但是这样会扩大空间复杂度。

冒泡排序的时间复杂度和空间复杂度:

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

在数据本来就有序的情况下,冒泡排序的最优时间复杂度为:O(N),即仅仅比较了数据大小,没有进行交换,所以我们可以设计一个“探子”flag去打探数据是否交换,如果进行一次内循环发现没有交换,就说明数据本来就有序,便直接退出外循环,节省时间


2.选择排序

选择排序相较于冒泡排序,它是能够一次确定头尾两个位置元素的排序方式:

先定义begin和end两个标志点,分别代表要排序数据的头和尾,然后默认一段乱序数据的第一个元素同时为最小值min、最大值max,然后从第二个数据开始分别去和它们比较,更小的值替换min,更大的值替换max,循环一次便能找到当前序列的最大值和最小值,然后再去分别和头尾元素交换,如此循环便可得出有序序列。

原码如下:

void SelectSort(int* a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int 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--;
	}
}

不难看出,内层循环每进行一次,就能确定最大值和最小值同时标志点begin和end向内移动,直到两者相遇,说明排序已经完成

值得注意的是,如果恰好第一个数据就是最大值,那么maxi将得不到最大值的下标,而此时我们先交换了最小值和头元素,作为头元素的最大值的下标便变成了mini所以要进行一次判断,如果最大值是头元素,就在交换了头元素和最小值之后,将mini赋值给maxi

选择排序的时间复杂度和空间复杂度:

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

3.堆排序

我们知道堆有大堆和小堆两个特性,而我们的堆排序则采用大堆化小堆来进行排序。

 我们默认一个堆按从上到下,从左到右的顺序为排序的顺序,这就要求我们要将更大的数据往堆的右下角去移动,所以要建小堆,但是单纯只是建个小堆,可能会出现下一层的元素大于上一层的情况

所以我们要先建大堆,这样可以使根节点为为最大值,然后再将根节点与堆的最右下角元素交换,然后再将剩下的元素继续调整为大堆,如此循环往复的去交换,便可实现排序。

源码如下:

//堆向下调整
void AdjustDown(int* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && 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--;
	}
}

堆排序的时间复杂度和空间复杂度:

  • 时间复杂度O(N*logN)
  • 空间复杂度O(1)

4.插入排序

插入排序的原理是从第二个数据开始依次去和它前边的数据比较,如果前边的数据比它大,就进行交换,并继续向前比较,直到前边的数据比它小为止

源码入下:

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 (tmp < a[end])
            {
                a[end + 1] = a[end];
                --end;
            }
            else
                break;
        }
        a[end + 1] = tmp;
    }
}

 插入排序的时间复杂度和空间复杂度:

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

5.希尔排序

希儿排序实际上是对插入排序的一种优化:先选定一个整数n把待排序数据分成个若干组,所有距离为n的数据在同一组内,并对每一组内的数据进行排序。然后,取更小的整数n重复上述分组和排序的工作。直到当n == 1时,所有数据在同一组内进行一次插入排序

源码如下:

void ShellSort(int* a, int n)
{
	int gap = n;
	//gap > 1是预排序,目的是让数据接近有序
	//gap = 1是直接插入排序,目的是让数据有序
	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 (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
					break;
			}
			a[end + gap] = tmp;
		}
	}
}

 gap即为所需要的整数,一般情况下,先让他等于数据长度,然后每次排序时都除以3再+1。这个并不固定,只要能保证最后gap要为1即可。

希尔排序的时间复杂度和空间复杂度:

  •  时间复杂度:平均为O(N^1.3)
  • 空间复杂度:平均为O(1)

6.快速排序

先来看动图:

默认第一个元素为key位,然后头尾两个“先锋兵”开始行动,其中R先向左移动,去找到比key小的值并停下,然后L开始向右移动,找到比key大的值并停下,然后交换L和R所在位置的值,并让R开始继续移动,重复上述操作直到R和L相遇,交换相遇点与key的值,并让相遇点成为新的key

如此操作之后,我们不难看出最后key的左边的值都比key小,而右边的值都比key大。这样我们就唯一确定了key的位置。随后我们就需要去分别排序key的左半段和右半段了,很显然,这需要用到递归

源码如下:

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

快速排序的时间复杂度和空间复杂度:

  • 时间复杂度O(N*logN)
  • 空间复杂度O(N*logN~O(N))

7.归并排序

先看动图:

 

不难看出,归并排序也是类似于一组一组的排完序后再整体进行排序

假设我们将数据不断拆分为4组,每个小组内部先进行排序,而后1,2组排序,3,4组排序,最后排完序的两组在整体排序

很显然,这样的排序方式同样需要用到递归,而且用一个数组是无法完成的,所以我们需要新建一个数组进行排序,再将排好的数据拷贝回去

源码如下:

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;

	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	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("MergeSort->malloc");
		exit(-1);
	}
	
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

 定义mid将数据拆分为两组进行两组之间的排序,将排好的顺序记录在新数组tmp中,最后再利用memcpy拷贝回数组a中

值得注意的是,在每个子递归函数中,所递归的数据的头都是begin,尾是end,所以拷贝时要注意地址以及长度的写法

归并排序的时间复杂度和空间复杂度:

  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(N)

三.总结

七大排序的基础知识到这里就介绍完啦,而数据结构初阶的知识到这里也全部落下帷幕啦

喜欢博主文章的小伙伴记得一键三连哦!

点关注不迷路,博主带你玩转编程!

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

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

相关文章

眼镜用超声波清洗机好不好?洗眼镜比较好用的超声波清洗机推荐

眼镜是我们日常生活中必不可少的用品&#xff0c;它不仅能帮助我们矫正视力&#xff0c;更是我们与外界进行交流的重要工具。然而&#xff0c;随着眼镜的长时间佩戴&#xff0c;眼镜表面容易积累灰尘、污垢和细菌&#xff0c;这不仅影响了我们的视野&#xff0c;也可能对眼睛健…

Docker(一)简介和基本概念:什么是 Docker?用它会带来什么样的好处?

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; 一、简介 本章将带领你进入 Docker 的世界。 什么是 Docker&#xff1f; 用它会带来什么样的好处&#xff1f; 好吧&#xff0c;让我们带…

Python连接数据库的梳理

我们通常用的数据库类型主要有关系型数据库&#xff0c;非关系型数据库等&#xff0c;其中关系型数据库主要有Microsoft SQL Server ,MySQL,Oracle&#xff0c;SQLite等&#xff0c;常用的非关系型数据库包括Redis、DynamoDB&#xff0c;MongoDB等 ​​​​​​​ 一 关系型…

hdu 3709 Balanced Number

Balanced Number 题意 定义一个非负整数在第 p p p 位为 p i v o t pivot pivot 的权重为&#xff1a;这个数位的值 \times 这个数位到 p i v o t pivot pivot 的距离 之和。如果在 p i v o t pivot pivot 左边的权重等于在 p i v o t pivot pivot 右边的权重&#xf…

uni-app小程序:文件下载打开文件方法苹果安卓都适用

api: const filetype e.substr(e.lastIndexOf(.)1)//获取文件地址的类型 console.log(文档,filetype) uni.downloadFile({url: e,//e是图片地址success(res) {console.log(res)if (res.statusCode 200) {console.log(下载成功,);var filePath encodeURI(res.tempFilePath);…

python开发之远程开发工具对比

前言 除了本地开发外&#xff0c;还有一种常见的开发方式就是远程开发&#xff0c;一般情况是一台Windows或mac笔记本作为日常使用的电脑&#xff0c;另有一台linux服务器作为开发服务器。开发服务器的性能往往较强&#xff0c;这样远程开发的方式一方面可以让我们在习惯的系统…

Python网络爬虫步骤是什么?新手小白必看 !

python网络爬虫步骤&#xff1a;首先准备所需库&#xff0c;编写爬虫调度程序&#xff1b;然后编写url管理器&#xff0c;并编写网页下载器&#xff1b;接着编写网页解析器&#xff1b;最后编写网页输出器即可。 本教程操作环境&#xff1a;windows7系统、python3.9版&#xff…

Spring | Spring中的Bean--下

Spring中的Bean: 4.Bean的生命周期5.Bean的配装配式 ( 添加Bean到IOC容器的方式 依赖注入的方式 )5.1 基于XML的配置5.2 基于Annotation (注解) 的装配 (更常用&#xff09;5.3 自动装配 4.Bean的生命周期 Spring容器可以管理 singleton作用域的Bean的生命周期&#xff0c;在此…

【Github搭建网站】零基础零成本搭建个人Web网站~

Github网站&#xff1a;https://github.com/ 这是我个人搭建的网站&#xff1a;https://xf2001.github.io/xf/ 大家可以搭建完后发评论区看看&#xff01;&#xff01;&#xff01; 搭建教程&#xff1a;https://www.bilibili.com/video/BV1xc41147Vb/?spm_id_from333.999.0.0…

sql570 | 至少有5名下属的经理 | join on | group by | having

讲给一张表&#xff0c;表字段分别为 id 、姓名、部分、经理id&#xff0c;可能存在张三既是下属也是经理 现在找出下属起码有5名员工的经理 CREATE TABLE Employee (id INT,name VARCHAR(255),department VARCHAR(255),managerId INT );INSERT INTO Employee (id, name, depar…

HarmonyOS 页面跳转控制整个界面的转场动画

好 本文 我们来说 页面间的转场动画 就是 第一个界面到另一个界面 第一个界面的退场和第二个界面的进场效果 首先 我这里 创建了两个页面文件 Index.ets和AppView.ets index组件 编写代码如下 import router from "ohos.router" Entry Component struct Index {b…

架构的演进

1.1单体架构 单体架构也称之为单体系统或者是单体应用。就是一种把系统中所有的功能、模块耦合在一个应用中的架构方式。 存在的问题&#xff1a; 代码耦合&#xff1a;模块的边界模糊、依赖关系不清晰&#xff0c;整个项目非常复杂&#xff0c;每次修改代码都心惊胆战迭代困…

LeetCode.2788. 按分隔符拆分字符串

题目 题目链接 分析 题目的意思是给我们一个字符串数组和一个分隔符&#xff0c;让我们按照分隔符把字符串数组分割成新的字符串数组。 看到这个描述&#xff0c;这不就是直接就是利用 按照分隔符分割字符串的系统库函数split()&#xff0c;这个函数的意思就是 把一个字符串…

SpringBoot解决Slow HTTP慢速攻击漏洞

项目场景&#xff1a; 扫描到的漏洞截图&#xff1a; 攻击原理&#xff1a; Web应用在处理HTTP请求之前都要先接收完所有的HTTP头部&#xff0c;因为HTTP头部中包含了一些Web应用可能用到的重要的信息。攻击者利用这点&#xff0c;发起一个HTTP请求&#xff0c;一直不停的发送…

H3C交换机S6850配置M-LAG三层转发

正文共&#xff1a;1999 字 30 图&#xff0c;预估阅读时间&#xff1a;3 分钟 前面提到M-LAG是一种跨设备链路聚合技术&#xff0c;将两台物理设备在聚合层面虚拟成一台设备来实现跨设备链路聚合&#xff0c;从而提供设备级冗余保护和流量负载分担。 之前已经做了DRNI的三层转…

MySQL之外键约束和表关系

前言 一个项目中如果将所有的数据都存放在一张表中是不合理的&#xff0c;比如一个员工信息&#xff0c;公司只有2个部门&#xff0c;但是员工有1亿人&#xff0c;就意味着员工信息这张表中的部门字段的值需要重复存储&#xff0c;极大的浪费资源&#xff0c;因此可以定义一个…

突破性概念“整车智能”背后,比亚迪又在蓄力何方?

比亚迪再以“整车智能”的颠覆性创意惊艳我们&#xff0c;他们这次又在酝酿哪些革命性技术&#xff0c;引领行业&#xff1f; 2024年的比亚迪梦想日&#xff0c;为汽车行业带来了一次全新的飞跃。这家传统但很有实力&#xff0c;却又颇有野心的自主品牌车企&#xff0c;再次以开…

使用Python在本地生成助记词

新建并打开一个空文件夹 逐行 执行命令 python3 -m pip install --upgrade pippip3 install eth_accountpip3 install web3touch acco.py然后看到文件夹下面会有个acco.py文件 将把下面的代码粘贴到acco.py中保存。 import os from eth_account import Accountif __name__ …

AI视频智能识别技术在智慧农业大棚升级改造管理场景中的应用方案

一、需求分析 随着科技的进步和农业现代化的推进&#xff0c;智能化技术逐渐成为现代农业发展的重要支撑。农业大棚作为现代农业的重要组成部分&#xff0c;其智能化改造对于提高农业生产效率、降低成本、增加收益具有重要意义。利用先进的信息化手段来对农业大棚进行管理&…

防伪技术行业研究:年复合增长率约为10%

近年来&#xff0c;我国各种新的防伪技术不断涌现&#xff0c;部分防伪技术已经达到国际先进水平&#xff0c;并广泛应用于产品防伪、票证防伪等领域&#xff0c;推动了防伪行业的持续、健康发展。 常见的产品防伪技术有&#xff1a;隐形分子技术、二维码防伪、揭开留底防伪、安…