排序篇(三)----交换排序
排序篇(三)----交换排序
1.冒泡排序
基本思想:
通过不断地比较相邻的元素,将较大的元素往后移动,从而实现排序的目的。
具体的步骤如下:
- 从待排序的数组中选择相邻的两个元素进行比较,如果前一个元素大于后一个元素,则交换它们的位置。
- 继续比较下一对相邻的元素,重复上述步骤,直到将整个数组排序完成。
- 重复执行上述步骤,直到没有需要交换的元素,即数组已经完全排序。
冒泡排序的特点是每次只比较相邻的两个元素,每一轮排序都将最大的元素移动到最后,因此称为冒泡。整个排序过程类似于水泡从底部冒到顶部的过程,因此得以闻名.
//冒泡排序
void BubbleSort(int* a, int n)
{for (int i = 0; i < n; i++){int exchange = 1;for (int j = 1; j < n - i; j++){if (a[j - 1] > a[j]){Swap(&a[j - 1], &a[j]);exchange = 0;}}if (exchange)return;}
}
这里面有一个小小的优化,exchange如果在这一趟排序中没有被修改过,代表着这组数据是有序的,因此可以直接return返回.
代码解析:
- 在给定的冒泡排序函数中,参数a是要排序的数组,n是数组的长度。
- 算法的核心是两层循环。外层循环控制排序的轮数,每一轮都将最大的元素移动到最后。内层循环用于比较相邻的元素并进行交换。
- 内层循环中,通过比较a[j-1]和a[j]的大小来判断是否需要交换它们的位置。如果a[j-1]大于a[j],则交换它们的位置,并将exchange标志设置为0,表示本轮循环有元素交换位置。如果没有发生交换,说明数组已经有序,可以提前结束排序。
- 外层循环每执行一轮,就会将最大的元素移动到最后,所以内层循环的范围会逐渐减小。每一轮排序都可以确定一个最大的元素的位置,所以外层循环只需要执行n次。
冒泡排序的特性总结:
- 冒泡排序是一种非常容易理解的排序
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
2.快速排序(递归)
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止 .
void QuickSort(int* a, int left, int right)
{
// 假设按照升序对array数组中[left, right)区间中的元素进行排序if (right <= left)return;
// 按照基准值对array数组的 [left, right)区间中的元素进行划分//int keyi = PartSort1(a, left, right);//int keyi = PartSort2(a, left, right);int keyi = PartSort3(a, left, right);
// 划分成功后以div为边界形成了左右两部分 [left, keyi-1) 和 [keyi+1, right)
// 递归排[left, keyi-1)QuickSort(a, left, keyi - 1);
// 递归排[keyi+1, right)QuickSort(a, keyi + 1, right);
}
上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,在写递归框架时想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。
2.1hoare版本快排
int PartSort1(int* a, int left, int right)
{//三数取中(优化)//int keyi = NumBers(a, left, right);//Swap(&a[keyi], &a[left]);int key = left;while (left < right){while (left < right && a[left] <= a[right]){right--;}while (left < right && a[left] <= a[right]){left++;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[key]);return left;
}
代码解析:
- 首先,定义一个变量key,用于保存基准值的下标,初始值为left。
- 进入一个循环,循环条件是left < right,即左右指针没有相遇。
- 在循环中,首先从右边开始,找到第一个小于等于基准值的元素的下标,将right指针左移,直到找到符合条件的元素或者left和right相遇。
- 然后从左边开始,找到第一个大于基准值的元素的下标,将left指针右移,直到找到符合条件的元素或者left和right相遇。
- 如果left < right,说明找到了需要交换的元素,将a[left]和a[right]交换位置。
- 重复步骤3到步骤5,直到left和right相遇。
- 最后,将基准值a[key]和a[left]交换位置,将基准值放在正确的位置上。
- 返回分割点的下标left。
实现了一次快速排序的分割操作,将数组分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。然后再通过递归调用这个函数,这就是hoare版的快排.
2.2挖坑法
int PartSort2(int* a, int left, int right)
{//三数取中优化//int keyi = NumBers(a, left, right);//Swap(&a[keyi], &a[left]);int key = a[left];int hole = left;//为第一个坑while (left < right){while (left < right && key <= a[right]){--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;
}
代码解析:
- 定义一个变量key,用于保存基准值,初始值为a[left]。
- 定义一个变量hole,用于保存空洞的位置,初始值为left。
- 进入一个循环,循环条件是left < right,即左右指针没有相遇。
- 在循环中,首先从右边开始,找到第一个小于基准值的元素的下标,将right指针左移,直到找到符合条件的元素或者left和right相遇。
- 将a[right]的值赋给a[hole],将空洞的位置移动到right。
- 然后从左边开始,找到第一个大于基准值的元素的下标,将left指针右移,直到找到符合条件的元素或者left和right相遇。
- 将a[left]的值赋给a[hole],将空洞的位置移动到left。
- 重复步骤4到步骤7,直到left和right相遇。
- 最后,将基准值key放入空洞的位置a[hole],将基准值放在正确的位置上。
- 返回空洞的位置hole。
同样实现了将数组分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。
2.3双指针法
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{//三数取中优化//int midi = NumBers(a, left, right);//Swap(&a[left], &a[midi]);int prev = left;int cur = prev + 1;int keyi = left;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}++cur;}Swap(&a[prev], &a[keyi]);return prev;
}
代码解析:
- 定义两个指针prev和cur,分别指向left和left+1。
- 定义一个变量keyi,用于保存基准值的下标,初始值为left。
- 进入一个循环,循环条件是cur <= right,即cur指针没有越界。
- 在循环中,如果a[cur]小于基准值a[keyi],则将prev指针右移一位,并交换a[prev]和a[cur]的值,保证prev指针之前的元素都小于基准值。
- 将cur指针右移一位。
- 重复步骤4到步骤6,直到cur指针越界。
- 最后,将基准值a[keyi]和a[prev]交换位置,将基准值放在正确的位置上。
- 返回分割点的下标prev。
同样实现了将数组分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。
以上三种方法均可实现快速排序,没有谁优谁劣,挑取自己便于理解的就行
2.4三数取中优化
三数取中是为了选择一个更好的基准值,以提高快速排序的效率。在快速排序中,选择一个合适的基准值是非常重要的,它决定了每次分割的平衡性。
快速排序的基本思想是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的小,然后再对这两部分分别进行快速排序,递归地进行下去,直到整个序列有序。
如果每次选择的基准值都是最左边或最右边的元素,那么在某些情况下,快速排序的效率可能会降低。例如,当待排序序列已经有序时,如果每次选择的基准值都是最左边或最右边的元素,那么每次分割得到的两个子序列的长度差可能会非常大,导致递归深度增加,快速排序的效率降低。
而通过三数取中的优化,可以选择一个更好的基准值,使得每次分割得到的两个子序列的长度差更小,从而提高快速排序的效率。
具体来说,三数取中的优化是选择待排序序列的左端、右端和中间位置的三个元素,然后取它们的中值作为基准值。这样选择的基准值相对于最左边或最右边的元素,更接近整个序列的中间位置,可以更好地平衡分割后的两个子序列的长度,从而提高快速排序的效率。
通过三数取中的优化,可以减少递归深度,提高分割的平衡性,使得快速排序的效率更稳定,适用于各种不同的输入情况。
//三数取中
int NumBers(int* a, int left, int right)
{int mid = (left + right) / 2;// left mid rightif (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] > a[right]) // mid是最大值{return left;}else{return right;}}else // a[left] > a[mid]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]) // mid是最小{return left;}else{return right;}}
}
2.5小区间优化
小区间优化是指在快速排序中,当待排序的子序列的长度小于一定阈值时,不再继续使用快速排序,而是转而使用插入排序。
void QuickSort(int* a, int left, int right)
{if (right <= left)return;if(right - left + 1 > 10){int keyi = PartSort3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}else{InsertSort(a + left,right - left + 1);}
}
小区间优化的好处:
- 减少递归深度:使用插入排序来处理较小的子序列,可以减少递归的深度,从而减少了函数调用的开销。
- 提高局部性:插入排序是一种稳定的排序算法,它具有良好的局部性,可以充分利用已经有序的部分序列。对于较小的子序列,插入排序的效率更高。
- 减少分割次数:对于较小的子序列,使用插入排序可以减少分割的次数。快速排序的分割操作需要移动元素,而插入排序只需要进行元素的比较和交换,因此在较小的子序列中使用插入排序可以减少分割操作的次数。
小区间优化可以在一定程度上提高快速排序的性能。它通过减少递归深度、提高局部性和减少分割次数来优化算法的效率,特别适用于处理较小的子序列。
3.快速排序(非递归)
这里需要借助栈的来实现非递归.关于栈详情见:数据结构剖析–栈
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{Stack st;StackInit(&st);StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st)){int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);int keyi = PartSort3(a, begin, end);if (keyi + 1 < end){StackPush(&st, end);StackPush(&st, keyi + 1);}if (begin < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, begin);}}StackDestroy(&st);
}
代码解析:
- 将整个序列的起始和结束位置入栈。然后,进入循环,不断从栈中取出子序列的起始和结束位置。
- 在每次循环中,通过PartSort3函数将当前子序列分割成两部分,并得到基准值的下标keyi。如果基准值右边的子序列长度大于1,则将右边子序列的起始和结束位置入栈。如果基准值左边的子序列长度大于1,则将左边子序列的起始和结束位置入栈。
- 循环继续,直到栈为空,表示所有的子序列都已经排序完成。
通过使用栈来模拟递归的过程,非递归实现避免了递归调用的开销,提高了快速排序的效率。
快速排序的特性总结:
-
快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
-
时间复杂度:O(N*logN)
-
空间复杂度:O(logN)
-
稳定性:不稳定
相关文章:
排序篇(三)----交换排序
排序篇(三)----交换排序 1.冒泡排序 基本思想: 通过不断地比较相邻的元素,将较大的元素往后移动,从而实现排序的目的。 具体的步骤如下: 从待排序的数组中选择相邻的两个元素进行比较,如果前一个元素大于后一个元素&#…...
React antd Table点击下一页后selectedRows丢失之前页选择内容的问题
一、问题 使用了React antd 的<Table>标签,是这样记录选中的行id与行内容的: <TabledataSource{data.list}rowSelection{{selectedRowKeys: selectedIdsInSearchTab,onChange: this.onSelectChange,}} // 表格是否可复选,加 type: …...
蓝牙核心规范(V5.4)11.4-LE Audio 笔记之音频模型
专栏汇总网址:蓝牙篇之蓝牙核心规范学习笔记(V5.4)汇总_蓝牙核心规范中文版_心跳包的博客-CSDN博客 爬虫网站无德,任何非CSDN看到的这篇文章都是盗版网站,你也看不全。认准原始网址。!!! 从一开始,蓝牙低功耗(Bluetooth Low Energy,BLE)音频的开发就秉持着“以设…...
Spring Boot:利用JPA进行数据库的查删
目录标题 DAO 、Service 、 Controller 层控制器文件示例代码-单个查找查找成功示例代码-列表查找查找成功示例代码-删除删除成功 DAO 、Service 、 Controller 层 DAO 层负责数据库访问,它封装了对数据库的访问操作,例如查询、插入、更新和删除等。 Q…...
1711: 【穷举】满足条件的整数
题目描述 假设a、b、c均为整数(1<a,b,c<100),同时a<b,找出所有符合条件:a2 b2 n*c3的整数组。 按a从小到大的顺序输出所有满足条件的整数组(若a相同,则按b从小到大的顺序输出) 输入…...
【数据结构】堆的应用-----TopK问题
目录 一、前言 二、Top-k问题 💦解法一:暴力排序 💦解法二:建立N个数的堆 💦解法三:建立K个数的堆(最优解) 三、完整代码和视图 四、共勉 一、前言 在之前的文章中ÿ…...
QT之xml文件的读写
QT之xml文件的读写 简介用法举例 简介 QT的QDomDocument、QDomElement、QDomNode是Qt XML模块中的三个类,用于解析和操作XML文档。 1)QDomDocument类: QDomDocument类表示整个XML文档。它提供了解析XML文档的方法,如setContent(…...
C语言中的异常处理机制是什么?
C语言中的异常处理机制 C语言是一门强大而灵活的编程语言,它为程序员提供了广泛的控制权和自由度。然而,C语言本身并不提供像其他高级语言一样的内置异常处理机制,如Java中的try-catch或Python中的异常处理。因此,C语言程序员需要…...
Java中的并发编程模型和常用工具类
本文主要介绍了Java中的并发编程模型和常用工具类,首先阐述了并发编程的概念及其重要性,然后详细介绍了线程的基本概念、生命周期和状态转换、同步与互斥、死锁问题以及线程池的使用和实现原理。接着介绍了synchronized关键字和Lock接口的使用、原子变量…...
第10章 MySQL(一)
10.1 谈谈MySQL的架构 难度:★★ 重点:★ 白话解析 要想彻底的理解MySQL,它的架构一定要先弄清楚,当Java程序员通过JDBC或者Mybatis去执行一条SQL的时候,到底经历了什么。下边先看一幅图: 户端:Java程序员通过JDBC或者Mybatis去拿MySQL的驱动程序,实际上就是拿客户端。…...
英飞凌 Tricore 架构中断系统详解
本文以TC3系列MCU为例,先来了解中断源是如何产生的,再看一下CPU是如何处理中断源的。 AURIX TC3XX的中断路由模块 Interrupt Router (IR) 在TC3中,中断既可以被CPU处理,也可以被DMA处理,所以手册中不再把中断称为中断…...
单例模式:饿汉式
单例模式全局仅一个实例,用于获取公共的内容 头文件mglobalinfomgr.h class MGlobalInfoMgr {MGlobalInfoMgr();~MGlobalInfoMgr(); public:static MGlobalInfoMgr* GetInstance(); private:static MGlobalInfoMgr* _instance; }; 源文件mglobalinfomgr.cpp MGl…...
什么是视图
目录 一、什么是视图 二、视图的作用 三、创建视图 四、使用视图 1.使用视图查询员工信息 五、注意事项 六、补充 一、什么是视图 视图是基于查询的虚拟表,是一个逻辑表,本身并不包含数据。同真实的表一样,视图包含一系列带有名称的列…...
C++——list(2)
作者:几冬雪来 时间:2023年9月28日 内容:C——list内容讲解 目录 前言: list的const迭代器: const的iterator: const迭代器: operator->: 拷贝构造: 迭代器接口补充&…...
Django基础讲解-路由控制器和视图(Django-02)
一 路由控制器 参考链接: Django源码阅读:路由(二) - 知乎 Route路由, 是一种映射关系!路由是把客户端请求的 url路径与视图进行绑定 映射的一种关系。 这个/timer通过路由控制器最终匹配到myapp.views中的视图函数 …...
【算法题】2873. 有序三元组中的最大值 I
题目: 给你一个下标从 0 开始的整数数组 nums 。 请你从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。如果所有满足条件的三元组的值都是负数,则返回 0 。 下标三元组 (i, j, k) 的值等于 (nums[i]…...
HTML5 跨屏前端框架 Amaze UI
Amaze UI采用国际最前沿的“组件式开发”以及“移动优先”的设计理念,基于其丰富的组件,开发者可通过简单拼装即可快速构建出HTML5网页应用,上线仅半年,Amaze UI就成为了国内最流行的前端框架,目前在Github上收获Star数…...
EXCEL会计记账报表财务软件企业公司做账系统凭证自动生成报表
本系统基于VBA编程设计,具有界面简洁美观,操作方便快捷,功能完备实用的特点,系统分为基本信息、凭证处理、账簿查询、会计报表、固定资产管理、系统管理、凭证数据库七大模块,您只需要录入记账凭证,就可以自…...
Can‘t pickle <class ‘__main__.Test‘>: it‘s not the same object as __main__.Test
目录 原因1 类名重复了 案例1 变量名和类名重复 原因1 类名重复了 检查项目代码,是不是其他地方有同名类。 案例1 变量名和类名重复 转自:python3报错Cant pickle <class __main__.Test>: its not the same object as __main__.Test解决 - 知乎…...
第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和
第五十六天| 第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和 一、1143. 最长公共子序列 题目链接: 题目介绍: 思路: 本题和“最长重复子数组”区别在于**这里不要求是连续的了,但要有相对顺序*…...
腾讯云服务器南京地域详细介绍、测试IP和Ping值测速
腾讯云服务器南京地域怎么样?南京地域很不错,正好处于中间的位置,南方北方用户均可以选择,网络延迟更低速度更快,并且目前南京地域有活动,南京地域可用区可选南京一区、南京二区和南京三区,腾讯…...
理解CSS的层叠性和继承性
CSS的层叠性(cascading)指的是在同一元素上应用多个样式时,不同样式之间的优先级别以及如何进行组合和冲突解决的规则。具体来说,CSS采用的是“选择器优先级”规则来判断哪个样式优先级更高,如果多个样式的优先级相同&…...
OSI体系结构和TCP/IP体系结构
在第一章( 计网第一章 )的时候,曾经提到过OSI体系结构和TCP/IP体系结构,并对它们进行了简单的对比。这篇博客在其基础上进行更深层次的理解。 一.OSI体系结构: 通信子网: 计算机网络在逻辑功能上可以分为…...
侯捷 C++ STL标准库和泛型编程 —— 8 适配器
8 适配器 适配器 Adapter 只是一个小变化,比如改个接口,函数名称等等其出现在三个地方:仿函数适配器,迭代器适配器,容器适配器可以使用继承 / 复合的两种方式实现,STL中都用复合 其思想就是将该记的东西记…...
每日一题 416 分割等和子集(01背包)
题目 分割等和子集 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 示例 1: 输入:nums [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] …...
U盘插上就显示让格式化是坏了吗?
U盘以其体积小巧、存储容量大、读写速度快的特点,在各种工作和个人使用场合中得到了广泛应用,因此深得用户好评。然而,在日常使用U盘的过程中,经常会遇到一些问题和挑战。今天,我将为大家详细解释U盘出现要求格式化的现…...
分布式应用程序协调服务 ZooKeeper 详解
目录 1、ZooKeeper简介 2、ZooKeeper的使用场景 3、ZooKeeper设计目的 4、ZooKeeper数据模型 5、ZooKeeper几个重要概念 5.1、ZooKeeper Session 5.2、ZooKeeper Watch 5.3、Consistency Guarantees 6、ZooKeeper的工作原理 6.1、Leader Election 6.2、Leader工作流…...
Anniversary party(树形dp 基础题)
1.题目大意 There is going to be a party to celebrate the 80-th Anniversary of the Ural State University. The University has a hierarchical structure of employees. It means that the supervisor relation forms a tree rooted at the rector V. E. Tretyakov. In …...
Junit的常用操作
注:本篇文章讲解的是junit5 目录 Juint是什么 Juint需要导入的依赖 Juint常用注解 Junit执行顺序 参数化 断言 测试套件 Juint是什么 Juint 是 Java 的一个单元测试框架. 也是回归测试框架. 使用 Junit 能让我们快速的完成单元测试。 注意:Junit 测试也是程序…...
Elasticsearch安装并使用Postman访问
Elasticsearch,一个强大的开源搜索和分析引擎,已经在全球范围内被广泛应用于各种场景,包括网站搜索、日志分析、实时应用等。由于其强大的功能和灵活性,Elasticsearch 已经成为大数据处理的重要工具。然而,对于许多初次…...
做效果图网站有哪些/什么是seo网站优化
Office成长课堂 点击左上角蓝字快速关注表格素材下载链接:https://pan.baidu.com/s/1bcuTsgrh0-Ge9vEPXG0Liw 提取码:72ig复制链接至电脑浏览器地址栏打开。案例检查字符串最右边的字符是文本字符,还是数字,如果是数字,…...
做网站要注意的/seo优化外包公司
1.LocationManagerProxy 获取当前Context 创建一个LocationManagerProxy 变量 mAMapLocManager LocationManagerProxy.getInstance(this); 2.mAMapLocManager.requestLocationUpdates(LocationProviderProxy.AMapNetwork, 5000, 10, this); //设定 精度 5000m 监听器为当…...
网站 域名 独立 一级/网络舆情优化公司
结构体数组 struct 结构名 变量名[数组大小] #include <iostream> #include <Windows.h>using namespace std;struct student {char name[32];int age; };int main(void) {//定义了一个结构体数组, 包含2个学生struct student a[2];//第一个学生赋值a[0] { &quo…...
织梦网站修改教程/搜索引擎收录提交入口
随着WiFi的普及,越来越多的人开始使用无线路由器进行无线上网,比如笔记本电脑,智能手机都可以共享一个路由器进行上网,有时候,我们在用笔记本进行无线WiFi上网的时候,电脑右下角的无线连接图标明明显示连接…...
哪些网站可以做国外生意/谷歌浏览器app
git rm index.html //删除暂存区的index.html 可直接用这个方法,不用第一个方法...
dw中网站统计总访问量怎么做/冯站长之家官网
由于继电器不能直接用数字I/O驱动,因此常通过单片机IO口输出控制信号,控制三极管的截止或饱和来驱动继电器, 注意:由于三级管的功耗基本都消耗在集电极上,从半导体结构上看,晶体管的C极面积最大,…...