数据结构——排序

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

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

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


目录

一.为什么要排序

二.七大排序

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;}elsebreak;}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;}elsebreak;}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++];elsetmp[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)

三.总结

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

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

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

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

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

相关文章

仰暮计划|“学校四周无围墙,教室通风望天窗”

一九七二年高中毕业&#xff0c;我成了回乡青年。玉米地里抡过锄&#xff0c;当阳峪村烧白干&#xff0c;化肥厂内装卸车&#xff0c;深山修渠扛石块。一九七四年十月&#xff0c;村革委会主任找我谈话&#xff0c;“回乡二年来&#xff0c;你无论是政治思想改造&#xff0c;还…

Python爬虫从入门到入狱系列合集

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

ES框架详解

ES框架详解 1、全文检索的介绍 ​ 那么对于一般的公司&#xff0c;初期是没有那么多数据的&#xff0c;所以很多公司更倾向于使用传统的数据库&#xff1a;mysql&#xff1b;比如我们要查找关键字”传智播客“&#xff0c;那么查询的方式大概就是:select * from table where …

公平与公正对团队的好处

公平与公正对团队的好处 如何联系我 作者&#xff1a;鲁伟林 邮箱&#xff1a;thinking_fioa163.com或vlinyes163.com 版权声明&#xff1a;文章和记录为个人所有&#xff0c;如果转载或个人学习&#xff0c;需注明出处&#xff0c;不得用于商业盈利行为。 一、什么是公平…

网络文件共享ftp

一&#xff0c;存储类型 &#xff08;一&#xff09;三种存储类型介绍 直连式存储&#xff1a;Direct-Attached Storage&#xff0c;简称DAS 直连&#xff1a;硬盘加服务器 存储区域网络&#xff1a;Storage Area Network&#xff0c;简称SAN&#xff08;可以使用空间&#…

Android-三方框架的源码

ARouter Arouter的整体思路是moduelA通过中间人ARouter把路由信息的存到仓库WareHouse&#xff1b;moduleB发起路由时&#xff0c;再通过中间人ARouter从仓库WareHouse取出路由信息&#xff0c;这要就实现了没有依赖的两者之间的跳转与通信。其中涉及Activity的跳转、服务prov…

tomcat与servlet

目录 一、Http服务器 二、tomcat 1、概念 2、tomcat解压缩文件 &#xff08;1&#xff09;bin文件夹 &#xff08;2&#xff09;conf文件夹 &#xff08;3&#xff09;logs &#xff08;4&#xff09;webapps 3、借助tomcat服务器访问网页 三、servlet 1、概念 2、s…

在微信公众号上怎么做报名链接_让你的活动报名与众不同

微信公众号报名链接&#xff1a;引领潮流&#xff0c;让你的活动报名与众不同 在这个信息爆炸的时代&#xff0c;微信早已成为我们生活中不可或缺的一部分。而微信公众号&#xff0c;更是成为了企业和个人传递信息、推广品牌的重要平台。如何在众多的微信公众号中脱颖而出&…

流量控制与熔断利器:Sentinel介绍

这是《百图解码支付系统设计与实现》专栏系列文章中的第&#xff08;19&#xff09;篇&#xff0c;也是流量控制系列的第&#xff08;6&#xff09;篇。点击上方关注&#xff0c;深入了解支付系统的方方面面。 本篇聊聊流量控制与熔断利器Sentinel&#xff0c;背后的原理&…

CVE-2024-0195 利用分析

1. 漏洞介绍 SpiderFlow爬虫平台项目中spider-flow-web\src\main\java\org\spiderflow\controller\FunctionController.java文件的FunctionService.saveFunction函数调用了saveFunction函数&#xff0c;该调用了自定义函数validScript&#xff0c;该函数中用户能够控制 functi…

系统架构设计师

软考系统架构设计师笔记 专用的成电路&#xff08;Application Specific Integrated Circuit&#xff0c;ASIC) PTR记录&#xff1a;Pointer Record&#xff0c;常被用于反向地址解析&#xff0c;即通过IP地址查询服务器域名。 软件工程 软件开发模型 【增量模型的优点】 …

大模型 RAG 面试篇

1.LLMs 存在模型幻觉问题&#xff0c;请问如何处理&#xff1f; 检索LLM。 先用问题在领域数据库里检索到候选答案&#xff0c;再用LLM对答案进行加工。 2.基于LLM向量库的文档对话 思路是怎么样&#xff1f; 加载文件读取文本文本分割文本向量化问句向量化在文本向量中匹配…

配置中心原理和选型

为什么需要配置中心&#xff1f; 缺乏整体管理&#xff0c;是配置管理变得低效 处于运维管理的需求 很容易导致实例的配置出现不一致的地方 生产环境多个集群直接修改配置&#xff0c;导致不一致 配置和代码在一起&#xff0c;配置修改需要重新发布&#xff0c;非常低效 目的是…

【C语言】编译和链接深度剖析

文章目录 &#x1f4dd;前言&#x1f320; 翻译环境和运行环境&#x1f309;翻译环境 &#x1f320;预处理&#xff08;预编译&#xff09;&#x1f309;编译 &#x1f320;词法分析&#x1f320;语法分析 &#x1f309;语义分析&#x1f320;汇编 &#x1f309; 链接&#x1f…

【教程】集群搭建准备工作全流程

基于VMware创建虚拟机进行集群搭建&#xff0c;适用于hadoop/GreenPlum等集群 之前已经创建了三台虚拟机hadoop102&#xff0c;hadoop103&#xff0c;hadoop104来搭建hadoop集群&#xff0c;因为目前学习到了greemplum&#xff0c;因此新建三台虚拟机hadoop105&#xff0c;had…

springboot110作业管理系统

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的作业管理系统 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资…

vue.js安装

1:下载 Node.js 官网&#xff1a;https://nodejs.org/en/download 2:安装 node -v npm -v 3:配置 npm config set prefix "F:\node\node_global" npm config set cache "F:\node\node_cache" 按 win 键并输入“编辑系统环境变量”调出系统属性界面&a…

AWTK 开源串口屏开发(7) - 屏幕保护

现代屏幕其实并不需要屏幕保护&#xff0c;不过屏幕保护程序会衍生一些其它用途。比如&#xff1a; 保护隐私。长时间不操作&#xff0c;通过动画或者其它方式隐藏屏幕内容。数据安全。长时间不操作&#xff0c;需要输入密码才能恢复。美观/广告。长时间不操作&#xff0c;显示…

C++中的static(静态)

2014年1月19日 内容整理自The Cherno:C系列 2014年1月20日 内容整理自《程序设计教程&#xff1a;用C语言编程 第三版》 陈家骏 郑滔 -----------------------------------------------------------------------------------------------------------------------------…

JS执行顺序

众所周知&#xff0c;JavaScript 是单线程语言,只能同时执行做一件事(js只有一个线程&#xff0c;称之为main thread-主线程) 1.Javascript 运行机制 main thread 主线程和 call-stack 调用栈(执行栈)&#xff0c;所有的任务都会被放到调用栈等待主线程执行。 2.Javascript 任…