当前位置: 首页 > news >正文

一起学数据结构(10)——排序

从本文开始,通过若干篇文章展开对于数据结构中——排序的介绍。

1. 排序的概念:

         将一堆杂乱无章的数据,通过一定的规律顺序排列起来。即将一个无序序列排列成一个有序序(由小到大或者由大到小)的运算。

        在数据的排序中需要注意,如果需要排序的对象同时有多个数据域(例如对学生进行排序,往往有学号,班级等多个数据域),排序往往是针对于其中一个数据域进行的。

2. 排序的种类:

       对于排序,本文及下面的文章中将着重介绍下列给出的排序:插入排序、希尔排序、选择排序、冒泡排序、快速排序、归并排序、计数排序。

3. 插入排序:

3.1 思路分析:

        对于插入排序,其基本思想可以概括为:每一步都将待排序的对象,按照该对象与已有数据的大小关系,插入到前面已经排好序的一组数据的合适的位置中。因此,插入排序可以看作一个一边进行插入,一边保持已有序列有序的排序。

        为了便于理解插入排序的基本思想,下面给出一个例子:

        对于上面给出的例子,按照上面插入排序的基本思想来进行排序,则需要首先比较待插入元素5与数组中已有元素进行比较。直到插入到一个合适的位置。所以对上述的案例进行排序后,最终结果为;

       对于上面给出的插入排序的过程需要理解,并不是真的对数组不断插入新的元素进行排序。而是将数组中的一部分看作插入的部分,例如对于下面的数组:

       将已经有序的序列,即数组中的1,2,4,7称为有序序列。将后面的数组成的序列称之为无序序列。如果这个序列进行插入排序,只是将元素6看作待插入元素。通过不断比对待插入元素与数组中元素的大小关系,来调整元素6至合适的位置。

      为了方便后续编写插入排序的代码,将有序序列的最后一位的下标记为end,将待插入元素的下标记为end+1。所以,end一开始所对应的下标就是数组第一个元素的下标,即0,待插入元素的下标就是end+1=1,即数组中的第二个元素。对比调整后,数组中前两个元素会变为有序序列。接着按照上面的步骤循环,即令end = 1,即有序序列的第二个元素,或者说数组中的第一个元素。待插入元素的下标等于end+1=2,即数组中的第二个元素。。。。。。。

3.2 代码演示:

      通过上面给出的思路,可以总结出下面代码:

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

对于插入排序,因为没有开辟额外的空间,所以插入排序的空间复杂度为O(1),当数组完全逆序时,插入排序需要执行的次数可以看作一个等差数列,所以插入排序的时间复杂度为O(n^2) 

3.3 插入排序测试:

测试函数如下:

void TestInsertsort()
{int a[] = { 2,1,3,4,6,9,5,8 };int size = sizeof(a) / sizeof(int);InsertSort(a, size);ArrayPrint(a, size);
}

其中函数ArrayPrint是用于打印数组的函数,原理过于简单,不予解释,运行效果如下:


 

4. 希尔排序: 

4.1 思路分析及代码演示:

        对于上面给出的插入排序中提到,如果插入排序需要排序的数组是逆序的,则插入排序的时间复杂度为O(n),如果需要排序的数组为顺序的,则时间复杂度为O(n),为了优化插入排序在排序逆序数组时较大的时间复杂度,可以尝试在对数组进行插入排序之间,先对数组进行依次预排序,让数组中某些元素是顺序的。对于先进行预处理,再进行一次插入排序的排序,就是文章本部分要介绍的希尔排序。例如下面的数组:

对于预排序,其步骤如下:首先确定一个间隔数,这里将这个间隔数命名为gab,通过利用 gab将数组分为若干个区间。并且对相邻区间的首元素进行一次类插入排序的操作。具体步骤将通过下面的图形进行演示:

1. 假设gab = 3,则利用gab分组后的数组可以表示为:

继续利用上面方法对数组中未分组的元素进行分组,可以表示为下面的图片,图中不同颜色的图形用于区分不同的组:

2. 接着,对于一组中的元素进行一次类插入排序的操作,即比较同一组的不同区间的首元素的大小关系,将小的元素放到前面。例如,对于红线组中的元素进行上述操作:

对于本部分的操作,可以利用插入排序的思想来实现,依旧定义end表示本组第一个元素的下标,例如红线组的9end+gab则表示本组下一个区间的首元素的坐标,再创建一个变量tmp用于存储end+gab所对应的元素。例如红线组的5。由于end所对应的元素>end+gab所对应的元素。所以让end代表的元素覆盖到end+gab的位置。再让tmp覆盖到end位置。该过程可用代码表示为:

void ShellSort(int* a, int n)
{int end = 0;int gab = 3;int tmp = a[end + gab];if (a[end] > tmp){a[end + gab] = a[end];}a[end] = tmp;
}

 但是,上面的过程并不完整,并且只能交换一次,加入遇到下面的情景:

假设 end所对应的元素为9end+gab所对应的元素为4,按照上面的代码交换一次后:

        可以看到,5,4这两个元素的大小关系还是不满足。因此,并不能向上面仅仅对 endend+gab的元素的大小关系进行判断,还需要对交换后前面的元素的关系重新进行一次判断,如果不满足则再次交换。方法为:再进行一次交换后,令end-gab,此时end-gab对应的元素为5(end-gab)+gab对应的元素为4,对二者再次进行一次交换。

       为了满足多次交换的目的需要利用到循环。所以对于循环的结束条件有两条:1是end < 0,2是元素的大小关系不符合。

      即使在加上上面的补充后,代码也只能用于单个组中一组endend+gab元素的交换,为了完成整租的交换,需要让end所对应的下标不断向后gab个位置。所以可以将上述代码优化为:

//希尔排序
void ShellSort(int* a, int n)
{int gab = 3;for (int end = 0; end < n - gab; end += gab){int tmp = a[end + gab];while (end >= 0){if (a[end] > tmp){a[end + gab] = a[end];end -= gab;}else{break;}}a[end + gab] = tmp;}
}

完善后的代码,可以一次性完成一组的预排序。但是在预排序的过程中需要对多组数据进行排序。通过对下面图形的观察可以得知,当end初始值就为2时,此时进行预排序的就是紫色线条对应的组,也就是需要预排序的最后一组。所以,可以在将上面的代码进行一次优化,让其能够处理多组预排序

并且,对于gab的值也是变化的,gab的值越大,数组中大的元素向后移动的距离越长,gab越小,移动的距离也越小,当gab=1,数组为有序。

代码如下:
 

//希尔排序
void ShellSort(int* a, int n)
{int gab = n;while (gab > 1){gab = gab / 2;for (int i = 0; i < gab; i++){for (int end = i; end < n - gab; end += gab){int tmp = a[end + gab];while (end >= 0){if (a[end] > tmp){a[end + gab] = a[end];end -= gab;}else{break;}}a[end + gab] = tmp;}}}
}

对于预排序部分的代码,还有另一种更简洁的部分,这里先给出相应代码,再进行逻辑分析:

//希尔排序
void ShellSort1(int* a, int n)
{int gab = n;while (gab > 1){gab = gab / 2;for (int i = 0; i < n - gab; i++){int end = i;int tmp = a[end + gab];while (end >= 0){if (a[end] > tmp){a[end + gab] = a[end];end -= gab;}else{break;}}a[end + gab] = tmp;}}
}

观察上面给出的代码,可以发现,相对于上面给出的预排序,这种预排序省略了一个for循环。逻辑也不同。对于现在给出的预排序,并不是按照严格分组,先进行完一组,再进行一组。而是在确定了gab之后,直接按照数组下标的顺序进行预排序,例如:

对于前面一种的预排序,是先对5,4进行预排序,再对5,9进行预排序,再对9,5进行预排序,此时,红线所对应的组完成了预排序,于是再对绿线所对应的组的元素开始预排序,顺序为:1,7,7,6

但是对于现在给出的预排序,预排序的顺序为:5,41,72,44,9 ,。。。。。。

由于希尔排序的时间复杂度的计算极其复杂,这里直接给出结论,希尔排序的时间复杂度大致在O(n^{1.3})左右。空间复杂度为O(1)

4.2 希尔排序测试:

测试函数如下:

void TestShellSort()
{int b[] = { 9,1,2,5,7,4,8,6,3,5 };int size = sizeof(b) / sizeof(int);ShellSort(b, size);printf("希尔排序:");ArrayPrint(b, size);
}

结果如下:

5. 冒泡排序:

5.1 代码演示:

        对于冒泡排序的相关原理,可以在文章C语言——冒泡排序和qsort排序-CSDN博客浏览,文章在本部分只给出冒泡排序的相关代码以及测试结果,对实现原理不做论述。

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

5.2 冒泡排序测试:

测试函数如下:

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

结果如下:

6. 堆排序 :

对于堆排序的相关原理及代码实现,在之前的文章如何利用堆来模拟堆排序-CSDN博客已经做了详细的解释,这里不再进行多余论述。

7. 选择排序:

7.1 思路分析以及代码演示:

        对于选择排序的原理,总结下来只有一句话,即每次排序时,选出数组中最小的值以及最大的值,将最小的值换到数组的最左边,最大的值换到数组的最右边。

       为了达成上述目的,可以创建min,max两个变量,通过遍历数组将二者选择出来,再通过交换函数,让两个值在数组中的位置分别达到最左边,最右边。

代码如下:

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

7.2 选择排序测试:

测试函数如下:

TestSelectSort()
{int d[] = { 7,8,1,4,5,9,2,3,6};int size = sizeof(d) / sizeof(int);SelectSort(d, size);printf("选择排序:");ArrayPrint(d, size);
}

 结果如下:

对于选择排序,显而易见,时间复杂度为O(n^2),空间复杂度为O(1)

相关文章:

一起学数据结构(10)——排序

从本文开始&#xff0c;通过若干篇文章展开对于数据结构中——排序的介绍。 1. 排序的概念&#xff1a; 将一堆杂乱无章的数据&#xff0c;通过一定的规律顺序排列起来。即将一个无序序列排列成一个有序序&#xff08;由小到大或者由大到小&#xff09;的运算。 在数据的排序中…...

php 数组基础/练习

数组 练习在最后 数组概述 概述与定义 数组中存储键值对 数组实际上是一个有序映射 key-value&#xff0c;可将其当成真正的数组、列表&#xff08;向量&#xff09;、散列表、字典、集合、栈、队列等 数组中的元素可以是任意类型的数据对象&#xff08;可以嵌套数组&#…...

Redbook Chapter 7: Query Optimization翻译批注

首先说明一下redbook上的几篇文章是做什么的。这几篇文章是通过几位作者对不同方面的论文进行阅读和筛选后&#xff0c;挑出其中具备代表性或者权威的论文来做分析&#xff0c;为读者提供阅读指导和建议&#xff0c;同时&#xff0c;也是对某个方面的论文进行高度的总结&#x…...

【分布式】大模型分布式训练入门与实践 - 04

大模型分布式训练 数据并行-Distributed Data Parallel1.1 背景1.2 PyTorch DDP1&#xff09; DDP训练流程2&#xff09;DistributedSampler3&#xff09;DataLoader: Parallelizing data loading4&#xff09;Data-parallel&#xff08;DP&#xff09;5&#xff09;DDP原理解析…...

欧拉图相关的生成与计数问题探究

最近学了一波国家集训队2018论文的最后一个专题。顺便带上了一些我的注解。 先放一波这个论文 1.基本概念 欧拉图问题是图论中的一类特殊的问题。在本文的介绍过程中&#xff0c;我们将会使用一些图 论术语。为了使本文叙述准确&#xff0c;本节将给出一些术语的定义。 定义…...

CSS3属性详解(一)文本 盒模型中的 box-ssize 属性 处理兼容性问题:私有前缀 边框 背景属性 渐变 前端开发入门笔记(七)

CSS3是用于为HTML文档添加样式和布局的最新版本的层叠样式表&#xff08;Cascading Style Sheets&#xff09;。下面是一些常用的CSS3属性及其详细解释&#xff1a; border-radius&#xff1a;设置元素的边框圆角的半径。可以使用四个值设置四个不同的圆角半径&#xff0c;也可…...

小程序:如何合理规划分包使主包不超过2M

背景 做过小程序项目的同学应该都有这样的经历&#xff0c;项目做着做着&#xff0c;突然发现代码包的大小超过了 2M&#xff0c;小程序无法提审&#xff0c;然后痛苦的删文件改代码来减少包大小。 虽然我们也知道小程序给我们提供了分包的功能可以减少主包的大小&#xff0c…...

迭代器的封装与反向迭代器

一、反向迭代器 在list模拟实现的过程中&#xff0c;第一次接触了迭代器的封装&#xff0c;将list的指针封装成了一个新的类型&#xff0c;并且以迭代器的基本功能对其进行了运算符重载 反向迭代器是对正向迭代器的封装&#xff0c;并且体现了泛型编程的思想&#xff0c;任意…...

PHP项目学习笔记-萤火商城https://www.yiovo.com/doc

萤火商城学习笔记 注意事项关于建表增加页面流程前台页面的数据列表数据下拉列表的数据 关于时间的处理前台界面数据处理 多年没有碰过php代码了&#xff0c;这个项目不错&#xff0c;想好好学习下&#xff0c;持续更新 注意事项 打开APP_DEBUG有些时候改了前台页面后&#x…...

我国有多少个港口?

港口是什么&#xff1f; 港口是海洋运输中不可或缺的重要设施之一&#xff0c;是连接陆路和水路运输的重要节点。港口通常是指位于沿海地区的水陆交通枢纽&#xff0c;是船舶停靠、装卸货物、储存物资和维修船只的场所。港口一般由码头、泊位、仓库、货场、客运站等设施组成&a…...

uniapp实现登录组件之外区域置灰并引导登录

实现需求 每个页面需要根据用户是否登录决定是否显示登陆组件,登录组件半屏底部显示,登录组件之外区域置灰,功能按钮点击之后引导提示登录.页面效果如下: 实现思路说明 设置登录组件背景颜色为灰色,将页面分成登录区域(底部)和非登陆区域(上面灰色显示部分), 置灰区域添加…...

抄表系统是如何抄到电表水表的数据的?

抄表系统是一种利用无线通信技术&#xff0c;实现远程读取电表水表数据的系统。抄表系统主要由三部分组成&#xff1a;电表水表、集中器和后台管理平台。接下来&#xff0c;小编来为大家详细的介绍下抄表系统是如何抄到电表水表的数据的&#xff0c;一起来看下吧! 电表水表是抄…...

Qt之自定义事件QEvent

在Qt中,自定义事件的步骤大概如下: 1.创建自定义事件,自定义事件需要继承QEvent 2.使用QEvent::registerEventType()注册自定义事件类型,事件的类型需要在 QEvent::User 和 QEvent::MaxUser 范围之间,在QEvent::User之前是预留给系统的事件 3.使用sendEvent() 和 postEv…...

项目管理week5——交个作业

...

5.5G移动通信技术

5.5G即5G-Advanced&#xff0c;是一种移动通信技术。 5.5G 是 5G 和 6G 之间的过渡阶段&#xff0c;将在速率、时延、连接规模和能耗方面全面超越现有 5G&#xff0c;有望实现下行万兆和上行千兆的峰值速率、毫秒级时延和低成本千亿物联。按照国际标准组织 3GPP 定义&#xff…...

chrony时间服务

目录 1.1.重要性 1.2. Linux的两个时钟 1.3. NTP 1.4. Chrony介绍 2.安装与配置 2.1.安装: 2.2. Chrony配置文件分析 3.实验 3.1实验1 3.2实验2 3.常见时区 1.1.重要性 ●由于IT系统中&#xff0c;准确的计时非常重要&#xff0c;有很多种原因需要准确计时: 。在网络…...

音乐制作软件 Studio One 6 mac中文版软件特点

Studio One mac是一款专业的音乐制作软件&#xff0c;该软件提供了全面的音频编辑和混音功能&#xff0c;包括录制、编曲、合成、采样等多种工具&#xff0c;可用于制作各种类型的音乐&#xff0c;如流行音乐、电子音乐、摇滚乐等。 Studio One mac软件特点 1. 直观易用的界面&…...

SpringBoot整合redis集群和redis单节点

// 连接redis单节点配置类Configuration public class RedisConfig {Value("${spring.redis.host}")private String host;Value("${spring.redis.port}")private Integer port;Value("${spring.redis.password}")private String password;/*** d…...

【ARM Coresight 系列文章19.1 -- Cortex-A720 PMU 详细介绍】

文章目录 概述Cortex-A720 PMU Features1.1 PMU 使用介绍1.2 Performance monitors events1.3 Performance Monitors Extension registers1.3.1 Performance monitors program1.4 Performance monitors interrupts1.5 Interaction with the Performance Monitoring Unit and De…...

FoneDog iOS Unlocker(ios解锁工具) 适用macos电脑

FoneDog iOS Unlocker是一款专业的iOS设备解锁工具&#xff0c;旨在帮助用户解决iOS设备上的解锁问题。该软件支持解锁各种锁定类型&#xff0c;如数字密码锁、手势密码锁、Touch ID和Face ID等&#xff0c;可以解除iPhone、iPad和iPod Touch等设备的锁定状态。FoneDog iOS Unl…...

ESUM模型:统一处理多拜耳模式的去马赛克技术

1. 去马赛克技术演进与多拜耳模式挑战去马赛克&#xff08;Demosaicing&#xff09;是数字图像处理中一项基础而关键的技术&#xff0c;它负责将传感器捕获的原始马赛克数据转换为全彩色图像。传统单拜耳&#xff08;Single-Bayer&#xff09;模式采用RGGB排列&#xff0c;每个…...

终极指南:如何用Snipe-IT免费开源系统解决企业IT资产追踪难题

终极指南&#xff1a;如何用Snipe-IT免费开源系统解决企业IT资产追踪难题 【免费下载链接】snipe-it A free open source IT asset/license management system 项目地址: https://gitcode.com/GitHub_Trending/sn/snipe-it 想象一下&#xff0c;你的公司有500台笔记本电…...

分布式系统与微服务架构:从核心原理到Java开发实战

1. 分布式系统平台&#xff1a;从背景到实战应用的深度剖析在软件开发领域&#xff0c;尤其是企业级应用和互联网服务的构建中&#xff0c;“分布式”早已不是一个新鲜词汇&#xff0c;而是工程师们日常打交道的核心范式。我们常听到J2EE、.NET、微服务这些名词&#xff0c;它们…...

给单片机新手的福利:拆解一个经典的篮球计分器项目,附Keil C代码逐行分析

51单片机篮球计分器项目深度解析&#xff1a;从状态机设计到数码管驱动实战 当你第一次拿到一个完整的单片机项目源码时&#xff0c;是否曾被那些看似复杂的函数调用和中断处理搞得一头雾水&#xff1f;本文将带你深入剖析一个经典的篮球计分器项目&#xff0c;不仅理解每行代…...

空间知识图谱与神经符号AI:让机器学习模型学会“思考”地图

1. 项目概述&#xff1a;当机器学习开始“思考”地图最近在GitHub上看到一个挺有意思的项目&#xff0c;叫“Thinking-with-Map”。光看名字&#xff0c;你可能会觉得这又是一个普通的GIS&#xff08;地理信息系统&#xff09;工具或者地图可视化库。但点进去仔细研究后&#x…...

游戏存档管理终极指南:告别背包焦虑的5大解决方案

游戏存档管理终极指南&#xff1a;告别背包焦虑的5大解决方案 【免费下载链接】TQVaultAE Extra bank space for Titan Quest Anniversary Edition 项目地址: https://gitcode.com/gh_mirrors/tq/TQVaultAE 还在为游戏中的装备堆积如山而烦恼吗&#xff1f;每次冒险归来…...

命令行视频生成工具tubecli:配置即代码的自动化视频制作实践

1. 项目概述与核心价值如果你经常需要处理视频内容&#xff0c;无论是做自媒体、产品演示还是内部培训&#xff0c;大概率都遇到过这样的场景&#xff1a;手头有一堆素材、脚本或者PPT&#xff0c;但把它们变成一段流畅的视频&#xff0c;总得在剪辑软件里折腾半天。更别提批量…...

初创公司如何借助Taotoken统一管理多个AI模型的API密钥

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 初创公司如何借助Taotoken统一管理多个AI模型的API密钥 对于技术资源有限的初创公司而言&#xff0c;在业务开发中引入多种大模型能…...

高效跨平台网盘直链解析工具:LinkSwift技术实现与部署指南

高效跨平台网盘直链解析工具&#xff1a;LinkSwift技术实现与部署指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / …...

Umi-OCR:完全免费开源的离线OCR神器,3分钟快速上手文字识别

Umi-OCR&#xff1a;完全免费开源的离线OCR神器&#xff0c;3分钟快速上手文字识别 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚&#xff0c;扫描/生成二维…...