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

数据结构初阶之排序(下)

前言

上一期内容中我们了解了基本排序中的插入与选择排序,今天我将为大家带来剩下的几种排序算法

快速排序

快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两⼦序列,左⼦序列中所有元素均⼩于基准值,右⼦序列中所有元素均⼤于基准值,然后最左右⼦序列重复该过程,直到所有元素都排列在相应位置上为⽌。

快速排序有着许多种实现方式,今天我们就hoare、挖坑法以及lomuto前后指针法来对其进行递归实现,同时还将运用数据结构中的栈来实现快速排序的非递归实现方法。

快速排序实现的主框架

//快速排序 
void QuickSort(int* a, int left, int right) 
{if (left >= right) {return;}//_QuickSort⽤于按照基准值将区间[left,right)中的元素进⾏划分 int meet = _QuickSort(a, left, right);QuickSort(a, left, meet - 1);QuickSort(a, meet + 1, right);
}

将区间中的元素进⾏划分的 _QuickSort ⽅法主要有以下⼏种实现⽅式:

我们可以将这些方法看做一个找基准值的过程:

hoare版本

思路:

1)创建左右指针,确定基准值

2)从右向左找出⽐基准值⼩的数据,从左向右找出⽐基准值⼤的数据,左右指针数据交换,进⼊下次循环

问题1:为什么跳出循环后right位置的值⼀定不⼤于key?

        当 left > right 时,即right⾛到left的左侧,⽽left扫描过的数据均不⼤于key,因此right此时指向的数据⼀定不⼤于key

如图:

问题2:为什么left和right指定的数据和key值相等时也要交换?

        相等的值参与交换确实有⼀些额外消耗。实际还有各种复杂的场景,假设数组中的数据⼤量重复时,⽆法进⾏有效的分割排序。

如图:

代码的实现
int _QuickSort(int* a, int left, int right) 
{int begin = left;int end = right;int keyi = left;++left;while (left <= right){// 右边找⼩ while (left <= right && a[right] > a[keyi]){--right;}// 右边找⼩ while (left <= right && a[left] < a[keyi]){++left;}if (left <= right){swap(&a[left++], &a[right--]);}}swap(&a[keyi], &a[right]);return right;
}

挖坑法

思路:

创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

我们通过一张图来了解:

代码的实现
int _QuickSort(int* a, int left, int right) 
{int mid = a[left];int hole = left;int key = a[hole];while (left < right){while (left < right && a[right] >= key) {--right;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){++left;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}

lomuto前后指针

思路:

创建前后指针,从左往右找⽐基准值⼩的进⾏交换,使得⼩的都排在基准值的左边。

如图:

代码的实现
int _QuickSort(int* a, int left, int right) 
{int prev = left, cur = left + 1;int key = left;while (cur <= right){if (a[cur] < a[key] && ++prev != cur) {swap(&a[cur], &a[prev]);}++cur;}swap(&a[key], &a[prev]);return prev;
}

快速排序的特征

1. 时间复杂度:O(nlogn)

2. 空间复杂度:O(logn)


快速排序的非递归版本

⾮递归版本的快速排序需要借助数据结构:

根据栈结构先进后出的原则,我们先将待排序数组的末尾元素先入栈,头元素后入栈。

随后分别取栈顶与栈底元素,利用循环来实现递归操作。

代码的实现

void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);// 单趟 int keyi = begin;int prev = begin;int cur = begin + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[keyi], &a[prev]);keyi = prev;// [begin, keyi-1] keyi [keyi+1, end] if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi + 1);}if (begin < keyi-1){STPush(&st, keyi-1);STPush(&st, begin);}}STDestroy(&st);
}

归并排序

归并排序的思想:

归并排序(MERGE-SORT)是建⽴在归并操作上的⼀种有效的排序算法,该算法是采⽤分治法(Divide  and Conquer)的⼀个⾮常典型的应⽤。将已有序的⼦序列合并,得到完全有序的序列;即先使每个⼦序列有序,再使⼦序列段间有序。若将两个有序表合并成⼀个有序表,称为⼆路归并。归并排序核⼼步骤(如下图):

利用递归分别将待排序的数组二等分割成n份,每次取一半,随后就分割后的两两数组进行合并操作(先比大小后放置)

代码的实现

void _MergeSort(int* a, int left, int right, int* tmp) 
{if (left >= right) {return;}int mid = (right + left) / 2;//[left,mid] [mid+1,right]_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = begin1;
//合并两个有序数组为⼀个数组 while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]) {tmp[index++] = a[begin1++];}else {tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}for (int i = left; i <= right; i++){a[i] = tmp[i];}
}
void MergeSort(int* a, int n) 
{int* tmp = new int[n];_MergeSort(a, 0, n - 1, tmp);delete[] tmp;
}

归并排序的特征

1. 时间复杂度:O(nlogn)

2. 空间复杂度:O(n)


测试代码

通过以下代码,我们可以得出各种排序算法的时间效率。

// 测试排序的性能对⽐  
void TestOP() 
{ srand(time(0)); const int N = 100000; 
int* a1 = (int*)malloc(sizeof(int)*N); int* a2 = (int*)malloc(sizeof(int)*N); int* a3 = (int*)malloc(sizeof(int)*N); int* a4 = (int*)malloc(sizeof(int)*N); int* a5 = (int*)malloc(sizeof(int)*N); int* a6 = (int*)malloc(sizeof(int)*N); int* a7 = (int*)malloc(sizeof(int)*N); for (int i = 0; i < N; ++i) { a1[i] = rand(); a2[i] = a1[i]; a3[i] = a1[i]; a4[i] = a1[i]; a5[i] = a1[i]; a6[i] = a1[i];a7[i] = a1[i]; } int begin1 = clock(); InsertSort(a1, N); int end1 = clock(); int begin2 = clock(); ShellSort(a2, N); int end2 = clock(); int begin3 = clock(); SelectSort(a3, N); int end3 = clock(); int begin4 = clock(); HeapSort(a4, N); int end4 = clock(); int begin5 = clock(); QuickSort(a5, 0, N-1); int end5 = clock(); int begin6 = clock(); MergeSort(a6, N); int end6 = clock(); int begin7 = clock(); BubbleSort(a7, N); int end7 = clock();printf("InsertSort:%d\n", end1 - begin1); printf("ShellSort:%d\n", end2 - begin2); 
printf("SelectSort:%d\n", end3 - begin3); printf("HeapSort:%d\n", end4 - begin4); printf("QuickSort:%d\n", end5 - begin5); printf("MergeSort:%d\n", end6 - begin6); printf("BubbleSort:%d\n", end7 - begin7); free(a1); free(a2); free(a3); free(a4); free(a5); free(a6); free(a7); 
} 

非比较排序

学习了以上的代码,那么有没有不通过比较大小就能实现的排序算法呢?有!

计数排序

计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应⽤。

操作步骤:

1)统计相同元素出现次数

2)根据统计的结果将序列回收到原来的序列中

通过两张图片来了解一下:

为了避免空间的无端浪费,我们将遍历待排数组中的最大最小值,后得出其间的差值,用其差值来申请空间大小以达到排序的效果。

代码的实现

void CountSort(int* a, int n)
{int min = a[0], max = a[0];for (int i = 1; i < n; i++){if (a[i] > max)max = a[i];if (a[i] < min)min = a[i];}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){perror("malloc fail");return;}
memset(count, 0, sizeof(int) * range);// 统计次数 for (int i = 0; i < n; i++){count[a[i] - min]++;`}// 排序 int j = 0;for (int i = 0; i < range; i++){while (count[i]--){a[j++] = i + min;}}
}

计数排序的特征

计数排序在数据范围集中时,效率很⾼,但是适⽤范围及场景有限。

时间复杂度:O(N + range)

空间复杂度:O(range)

稳定性:稳定

*计数排序仅适用于整数的排序


排序算法的稳定性及其复杂度综合分析

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的 相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,⽽在排序后的序列中,r[i]仍在r[j]之 前,则称这种排序算法是稳定的;否则称为不稳定的。


以上便是数据结构初阶的全部内容,感谢各位的支持!后面我将为大家带来C++有关的知识分享,敬请期待!

相关文章:

数据结构初阶之排序(下)

前言 上一期内容中我们了解了基本排序中的插入与选择排序&#xff0c;今天我将为大家带来剩下的几种排序算法 快速排序 快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法&#xff0c;其基本思想为&#xff1a;任取待排序元素序列中的某元素作为基准值&#xff0c;…...

RGB图像的读取与保存

目录 1、安装imageio 2、读取照片 3、保存照片 4、resize 5、示例代码 1、安装imageio pip install imageio -i https://pypi.tuna.tsinghua.edu.cn/simple 2、读取照片 import imageio img imageio.imread(image_path) 3、保存照片 import imageio import numpy as…...

江协科技51单片机学习- p35 AD/DA模拟/数字采样

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…...

C#裁剪图像的几种方法总结

前言 我们在上位机软件开发过程中经常需要裁剪图像&#xff0c;本文就是对c#中常见的裁剪图像方法进行总结。 1、克隆 直接调用Bitmap的Clone函数&#xff0c;然后指定需要裁剪的区域即可裁剪图像&#xff0c;该种方法不会损失精度 public static Bitmap CropImage_Clone(Bi…...

被遗忘的哑终端 —— 键盘键位演变的启发者

注&#xff1a;机翻&#xff0c;未校对。 The Forgotten World of Dumb Terminals 被遗忘的哑终端世界 A quick journey through the lost age of “glass teletypes.” 快速穿越失落的“玻璃电传打字机”时代。 From the earliest days of digital computers, researchers o…...

APACHE安装与应用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…...

预警器件控制思考

预警器件控制思考 最小示例思想 当读取到环境信息与环境阈值的时候, 我们预警系统就要根据这些信息做出判断,是否要启动器件。 最简单的就是&#xff0c; 举温度temp的例子, temp(温度)与temp_th(阈值), 通过判断, 得出是否要启动器件. 如果在一段时间内, 一直是环境异常, 我…...

[Day 43] 區塊鏈與人工智能的聯動應用:理論、技術與實踐

區塊鏈的隱私保護機制 隨著區塊鏈技術的廣泛應用&#xff0c;隱私保護成為了一個至關重要的問題。區塊鏈以其去中心化和透明性的特點&#xff0c;為數據管理和交易提供了新的方法。然而&#xff0c;這些特點也帶來了新的挑戰&#xff0c;尤其是在隱私保護方面。本文將深入探討…...

【星海随笔】路由器的启动过程

路由器的启动过程 1.加电之后&#xff0c;ROM运行加电自检程序&#xff08;Post&#xff09;&#xff0c;检查路由器的处理器、接口、内存等硬件设备。2.执行路由器中的启动程序(Bootstrap),搜索操作系统。路由器操作系统扩张部分可以从Flash RAM中装入&#xff0c;也可从 TFT…...

[翻译] Asset Administration Shells

关于资产管理外壳 (AAS) 资产管理外壳 (AAS) 是工业4.0中的关键概念&#xff0c;为产品、资源&#xff08;如设备&#xff09;和过程提供信息隐藏和更高层次的抽象。AAS 是技术和设备无关的机器可读描述&#xff0c;提供访问资产属性和功能的统一接口。与现有解决方案不同&…...

linux 常用磁盘维护命令

badblocks 功能说明&#xff1a;检查磁盘装置中损坏的区块。 语 法&#xff1a;badblocks [-svw][-b <区块大小>][-o <输出文件>][磁盘装置][磁盘区块数][启始区块] 补充说明&#xff1a;执行指令时须指定所要检查的磁盘装置&#xff0c;及此装置的磁盘区块数。…...

滑动窗口大总结!!!妈妈以后再也不担心我不会做滑动窗口啦~

写在前面&#xff1a;全部题都源于力扣 讲解题目一&#xff1a;最小覆盖子串题目二&#xff1a;字符串排列题目三&#xff1a;找所有字母异位词题目四&#xff1a;无重复字符的最长子串题目五&#xff1a;滑动窗口的最大值 讲解 滑动窗口算法技巧主要用来解决子数组问题&#…...

从地铁客流讲开来:客流统计与清分释义

一、常见的客流统计 1. 进站客流 定义&#xff1a;指在某个时间段内&#xff0c;乘客进入地铁站的数量。示例&#xff1a;如果某天早上8点到9点之间有5000人次进入地铁站&#xff0c;则这段时间内的进站客流为5000人次。 2. 出站客流 定义&#xff1a;指在某个时间段内&…...

《Excelize权威指南》新书发布

在数据洪流涌动的数字化时代&#xff0c;数据处理与分析已跃升为解锁无限洞察力的金钥匙&#xff0c;赋能商业智慧、重塑医疗健康版图、驱动教育科研创新。然而&#xff0c;当数据量级爆炸式增长&#xff0c;传统工具如 Excel 虽被誉为数据处理领域的常青树&#xff0c;其手动操…...

Go语言加Vue3零基础入门全栈班11 Go语言+gorm用户管理系统实战 2024年08月03日 课程笔记

概述 如果您没有Golang的基础&#xff0c;应该学习如下前置课程。 Golang零基础入门Golang面向对象编程Go Web 基础Go语言开发REST API接口_20240728Go语言操作MySQL开发用户管理系统API教程_20240729Redis零基础快速入门_20231227GoRedis开发用户管理系统API实战_20240730Mo…...

【设计模式】代理模式详解

1.简介 代理模式是常用的Java设计模式&#xff0c;该模式的特点是代理类与委托类共享相同的接口。代理类主要负责预处理消息、过滤消息、将消息转发给委托类&#xff0c;并在事后处理消息等。代理类与委托类之间通常存在关联关系&#xff0c;一个代理类对象与一个委托类对象关…...

Python变量和简单的数据类型

1、变量 massageHello python world! print(massage) massageHello world print(massage) 运行这个代码发现&#xff0c;同一个变量出现两个不同的结果 Hello python world! Hello world 在程序中&#xff0c;可随时修改变量的值&…...

切比雪夫距离

切比雪夫距离&#xff08;Chebyshev Distance&#xff09;&#xff0c;又称棋盘距离或最大值距离&#xff0c;是一种用于测量两个点之间距离的度量方法。在二维平面上&#xff0c;切比雪夫距离定义为两个点之间的最大坐标差值。其公式如下&#xff1a; DChebyshevmax⁡(∣x2−…...

计算机基础(Windows 10+Office 2016)教程 —— 第4章 计算机网络与Internet(下)

第4章 计算机网络与Internet 4.4 局域网4.4.1 局域网概述4.4.2 以太网4.4.3 令牌环网4.4.4 无线局域网 4.5 Internet4.5.1 Internet 概述4.5.2 Internet 的基本概念4.5.3 Internet 的接入4.5.4 万维网 4.6 Internet的应用4.6.1 电子邮件4.6.2 文件传输4.6.3 搜索引擎 4.4 局域网…...

机器学习用Python还是R?哪个更好一些?

选择使用Python还是R来进行机器学习取决于多个因素&#xff0c;包括个人偏好、项目需求以及可用的资源。这里我可以简要比较一下它们的优缺点&#xff1a; Python的优势&#xff1a; 通用性和灵活性&#xff1a; Python是一种通用编程语言&#xff0c;可以用于多种用途&#…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...