常见排序算法鉴赏(原理剖析+动图演示)

目录

一、冒泡排序(BubbleSort)

二、选择排序( SelectSort)

三、插入排序(InsertSort)

四、希尔排序(ShellSort)

五、堆排序

六、快排(QuickSort)

Hoare版本

前后指针法

优化之三数取中

七、归并排序(MergeSort)

八、计数排序(CountSoort)

九、小结


注:本文排序都是升序

一、冒泡排序(BubbleSort)

通过连续地比较与交换相邻元素实现排序,这个过程就像气泡从底部升到顶部一样, 因此得名冒泡排序。

冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大 小,如果“左元素>右元素”就交换二者。遍历完成后,最大的元素会被移动到数组的最右端。

代码实现:

//冒泡排序
void BubbleSort(int* arr, size_t size) {for (size_t i = 0; i < size - 1; i++){bool exchange = 0;for (size_t j = 0; j < size - 1 - i; j++){if (arr[j] > arr[j + 1]) {Swap(&arr[j], &arr[j + 1]);exchange = 1;}}if (exchange == 0) break;}
}

动图演示:

二、选择排序( SelectSort)

开启一个循环,每轮从未排序区间选择最小的元素,将其放到已排序区间的末尾:

  1. 初始状态下,所有元素未排序,即未排序(索引)区间为[0,𝑛−1]。
  2. 选取区间[0,𝑛−1]中的最小元素,将其与索引0处的元素交换。完成后,数组前1个元素已排序。
  3. 选取区间[1,𝑛−1]中的最小元素,将其与索引1处的元素交换。完成后,数组前2个元素已排序。
  4. 以此类推。经过𝑛−1轮选择与交换后,数组前𝑛−1个元素已排序。
  5. 仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。

代码实现:


//简单选择排序
void SelectSort1(int* arr, size_t size) {//外层循环:[0,size-1]for (size_t i = 0; i < size - 1; i++){//记录最小值的索引size_t mini = i;//内层循环:找到未排序区间[1,size-1]的最小值//需要找size-1个数for (size_t j = i + 1; j < size; j++){if (arr[mini] > arr[j])mini = j;}Swap(&arr[i], &arr[mini]);}
}

优化版本:

从区间两边缩小,区间最左侧放最小值,区间最右侧放最大值

void SelectSort2(int* arr, size_t size) {size_t left = 0, right = size - 1;while (left <= right) {//假设第一个索引的值既是最大值也是最小值size_t mini = left, maxi = left;//只需要从第二个值开始遍历 找新的最大值最小值for (size_t i = left + 1; i <= right; i++){if (arr[i] > arr[maxi]) maxi = i;if (arr[i] < arr[mini]) mini = i;}//交换值Swap(&arr[left], &arr[mini]);//这里需要多个判断 如果maxi == left 两数交换两次相当于没有交换if (maxi == left) maxi = mini;Swap(&arr[right], &arr[maxi]);left++;right--;}
}

动图演示:

三、插入排序(InsertSort)

与手动整理一副牌的过程非常相似。 具体来说,我们在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并将该元素插入到正确的位置。

代码实现:


//插入排序
void InsertSort(int* arr, int size) {//end走到倒数第二个就停下,无需再做比较for (size_t i = 0; i < size - 1; i++){//[0,end],end+1int end = i;int insertVal = arr[end + 1];while (end >= 0) {if (insertVal < arr[end]) {arr[end + 1] = arr[end];end--;}else {break;}}arr[end + 1] = insertVal;}}

 

动图演示:

四、希尔排序(ShellSort)

希尔排序又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成个gap组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后(gap / 3 + 1)重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。

这里可以得到一个结论:

gap越大,数据跳跃越快,越小的越容易到前面,但是越不接近有序

gap越小,数据跳跃越慢,越小的不更易到前面,但是越接近有序

代码实现:

//希尔排序
void ShellSort(int* arr, size_t size) {int gap = size;while (gap > 1) {gap = gap / 3 + 1;for (int i = 0; i < size - gap; i++){//[0,end],end+1int end = i;int insertVal = arr[end + gap];while (end >= 0) {if (insertVal < arr[end]) {arr[end + gap] = arr[end];end -= gap;}else {break;}}arr[end + gap] = insertVal;}}
}

五、堆排序

流程:

1)建堆 实现升序建大根堆 实现降序建小根堆

核心原理:

堆排序通过反复提取堆顶元素(极值)实现排序,堆的类型决定了提取元素的顺序:

  • 大顶堆:堆顶始终为当前最大值
  • 小顶堆:堆顶始终为当前最小值

2) 排序 利用堆删除思想排序

注:此流程图来源 hello‑algo.com

代码实现:

//向下调整算法,参数:数组 数组元素个数 开始向下调整的父节点索引
AdjustDown(HpDataType* arr, size_t size, size_t parent) {//找较大的子节点,假设左子节点较大size_t child = 2 * parent + 1;//child索引不断增大,但不会超过数组元素个数while (child < size) {//假设错误,右子节点更大,更新索引if (child + 1 < size && arr[child] < arr[child + 1]) {child++;}//如果父节点小于等于子节点,交换两节点的值if (arr[parent] <= arr[child]) {Swap(&arr[parent], &arr[child]);//更新父节点的索引,现在变成儿子了parent = child;//继续找儿子比较child = 2 * parent + 1;}else {break;}}
}void HeapSort(HpDataType* arr, int size) {// 建堆 升序建大根堆 降序建小根堆for (int i = (size - 2) / 2; i >= 0; i--) {AdjustDown(arr, size, i);}// 排序int end = size - 1;while (end > 0) {Swap(&arr[0], &arr[end]);  AdjustDown(arr, end, 0);   end--;}
}

六、快排(QuickSort)

Hoare版本

快排最早是由Hoare提出的,所以,他的经典思想我们有必要学习一下

有如下步骤:

1.基准元素选择

        选择数组第一个元素作为基准,记录基准索引keyi

2.双向指针扫描

  • 设置两个指针left和right,left从数组左端(begin)开始向右扫描,right从右端(end)开始向左扫描,一定要让右指针先走,这样才能保证较大值右移、较小值左移 
  •  right指针寻找第一个小于等于基准的元素,找到之后停止不动
  • left指针寻找第一个大于等于基准的元素,找到之后停止不动
  • 当两指针相遇或交叉时停止扫描       

3.  ​​​​​​元素交换

        当left < right时,交换arr[left]和arr[right]的值。这个交换操作能使较大值右移、较小值左移

4.分区完成判断

       重复步骤2-3直到left >= right,此时j所指位置即为当前分区的中间点。此时数组被划分           为:左区间:[begin,keyi-1]右区间:[keyi+1,end]

5.递归排序

        对左右两个子数组分别递归执行上述过程,直到子数组长度为1时终止递归

第一趟详细排序:

递归完成最后排序:

动图演示:

代码实现:

//Hoare版本快排
void QuickSort2(int* arr, int left, int right) {if (left >= right)return;//创建变量保存区间边界int keyi = left;int begin = left, end = right;while (left < right) {//右边找小while (left < right && arr[keyi] <= arr[right]) {--right;}//左边找大while (left < right && arr[keyi] >= arr[left]) {++left;}Swap(&arr[left], &arr[right]);}Swap(&arr[keyi], &arr[left]);keyi = left;// [begin, keyi-1]keyi[keyi+1, end]QuickSort2(arr, begin, keyi - 1);QuickSort2(arr, keyi + 1, end);}

前后指针法

动图演示:

核心思路:

1.选定最左侧元素为基准值并保存,将prev设置为left,cur设置为left+1

2.cur去寻找当前区间内比基准值小的元素

3.如果没找到,说明基准值右侧的元素都是大于基准值的,不需要其他操作,直接跳出循环即可

4.如果找到了先prev++,再将prev和cur位置的元素交换,然后cur++。

5.等到cur越界之后,说明遍历完序列中的所有元素了,将基准值位置的元素和prev位置的元素交换

6.最后返回prev即可

代码实现:

//前后指针法快排
void QuickSort1(int* arr, int left, int right) {if (left >= right)return;int prev = left, keyi = left;int cur = left + 1;while (cur <= right) {if (arr[cur] < arr[keyi] && prev++ != cur) {Swap(&arr[prev], &arr[cur]);}cur++;}Swap(&arr[prev], &arr[keyi]);keyi = prev;//[left, keyi - 1] keyi [keyi + 1, right]QuickSort1(arr, left, keyi - 1);QuickSort1(arr, keyi + 1, right);
}

优化之三数取中

如果遇到有序序列或者是接近有序序列,快排的效率就会接近O(n^2),原因是我们每次选择keyi都是区间左值,这样可能选取到极端值(比如最小或最大元素)作为基准,这样会导致分区不平衡。

三数取中,顾名思义:取三个数中第二大的数

代码实现:

//三数取中
int ThreeNumMid(int* arr, int left, int right) {int mid = (left + right) / 2;if (arr[mid] < arr[right]) {if (arr[mid] > arr[left]) {return mid;}else if(arr[left] < arr[right]) {return left;}else {return right;}}else {// arr[mid] > arr[right]if (arr[mid] < arr[left]) {return mid;}else if (arr[right] < arr[left]) {return left;}else {return right;}}
}

但是如果数据量过小,我们三数取中法又显得十分累赘,这种情况下,我们可以选择更高效的排序,插入排序

优化后的代码:

//Hoare版本快排
void QuickSort2(int* arr, int left, int right) {if (left >= right)return;// 小区间选择走插入,可以减少90%左右的递归if (right - left + 1 < 10){InsertSort(arr + left, right - left + 1);}else {//创建变量保存区间边界int midi = ThreeNumMid(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left;int begin = left, end = right;while (left < right) {//右边找小while (left < right && arr[keyi] <= arr[right]) {--right;}//左边找大while (left < right && arr[keyi] >= arr[left]) {++left;}Swap(&arr[left], &arr[right]);}Swap(&arr[keyi], &arr[left]);keyi = left;// [begin, keyi-1]keyi[keyi+1, end]QuickSort2(arr, begin, keyi - 1);QuickSort2(arr, keyi + 1, end);}//双指针法快排
void QuickSort1(int* arr, int left, int right) {if (left >= right)return;// 新增三数取中逻辑int mid = ThreeNumMid(arr, left, right);Swap(&arr[left], &arr[mid]);int keyi = left;int prev = left, cur = left + 1;while (cur <= right) {if (arr[cur] < arr[keyi] && prev++ != cur) {Swap(&arr[prev], &arr[cur]);}cur++;}Swap(&arr[prev], &arr[keyi]);keyi = prev;QuickSort1(arr, left, keyi - 1);QuickSort1(arr, keyi + 1, right);
}

七、归并排序(MergeSort)

归并排序利用分治的思想,分为划分区间归并排序两个阶段

1.划分区间阶段:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题

2.归并排序阶段:当子数组长度为1时终止划分,开始归并,持续地将左右两个较短的有序数组归并为一个较长的有序数组,直至结束

如下图所示:

观察发现,归并排序与二叉树后序遍历的递归顺序是一致的。 ‧ 后序遍历:先递归左子树,再递归右子树,最后处理根节点 ‧ 归并排序:先递归左子数组,再递归右子数组,最后处理合并

代码实现:


//归并排序
void _MergeSort(int* arr, int* tmp, int begin, int end) {//递归到最小子区间if (begin == end)return;int mid = (begin + end) / 2;//[begin,mid] [mid+1,end]_MergeSort(arr, tmp, begin, mid);_MergeSort(arr, tmp, mid+1, end);//归并排序 将更大的值尾插到tmp数组中//分出两个区间范围值int left1 = begin, right1 = mid;int left2 = mid + 1, right2 = end;int i = begin;//判断两个区间的值谁更小while (left1 <= right1 && left2 <= right2) {if (arr[left1] < arr[left2]) {tmp[i++] = arr[left1++];}else {tmp[i++] = arr[left2++];}}//任何一个区间结束,将剩余的直接尾插while (left1 <= right1) {tmp[i++] = arr[left1++];}while (left2 <= right2) {tmp[i++] = arr[left2++];}//将排序好的tmp拷贝到arr中memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}void MergeSort(int* arr, size_t size) {int* tmp = (int*)malloc(sizeof(int) * size);if (tmp == NULL) {perror("malloc() err!");return;}_MergeSort(arr, tmp, 0, size - 1);free(tmp);tmp = NULL;
}

动图演示:

八、计数排序(CountSoort)

核心原理:

统计每个数出现的次数,一个数出现几次,映射的位置值就加几次

这个排序算法有一定的局限性:

  • 只适用于整型排序
  • 只适用于数据集中的数据群
  • 不适用于数据较为分散的数据群

代码实现:

//计数排序
void CountSoort(int* arr, size_t size) {//找最大值和最小值int min = arr[0], max = arr[0];for (size_t i = 1; i < size; i++){if (arr[i] < min) min = arr[i];if (arr[i] > max) max = arr[i];}//创建临时数组用于统计次数int range = max - min + 1;int* tmp = (int*)malloc(sizeof(int) * range);if (tmp == NULL) {perror("malloc() err!");return;}memset(tmp, 0, sizeof(int) * range);//统计次数for (size_t i = 0; i < size; i++){tmp[arr[i] - min]++;}//排序int j = 0;for (size_t i = 0; i < range; i++){while (tmp[i]--) {arr[j++] = i + min;}}}

动图演示:

九、小结

算法的稳定性:稳定排序在完成排序后,相等元素在数组中的相对顺序不发生改变 

 看到了这里,麻烦点个赞和关注再走吧~

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

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

相关文章

鸿蒙跨平台框架ArkUI-X

01 引言 目前&#xff0c;移动端主流跨平台方案有Flutter、React Native、uni-app等等&#xff0c;还有刚推出不久的Compose-Multiplatform&#xff0c;真所谓是百花齐放。这些框架各有特点&#xff0c;技术实现各有差异&#xff0c;比如Flutter通过Dart编写的UI描述对接Flutte…

【科一】综合素质

职业理念&职业道德 &#xff08;职业理念中的教育观&#xff09; 提速个性创两全 素质教育是 以提高国民素质为根本宗旨促进学生个性发展以培养学生的创新精神和实践能力为重点面向全体学生促进学生全面发展 学习过人 教育者为中心 转向学习者为中心教会学生知识 转向 教会…

一招解决Pytorch GPU版本安装慢的问题

Pytorch是一个流行的深度学习框架&#xff0c;广泛应用于计算机视觉、自然语言处理等领域。安装Pytorch GPU版本可以充分利用GPU的并行计算能力&#xff0c;加速模型的训练和推理过程。接下来&#xff0c;我们将详细介绍如何在Windows操作系统上安装Pytorch GPU版本。 查看是否…

静态时序分析:SDC约束命令set_ideal_network详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 set_ideal_network命令可以将当前设计中的一组端口或引脚标记为理想网络源&#xff08;或者说设置端口或引脚的ideal_network_source属性为true&#xff09;&…

优先队列priority_queue应用

不讲概念&#xff01;&#xff01;只说用法&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 优先队列 priority_queue 换种话来说就是堆&#xff0c;只可以从中取到最大或者最小的值&#xff0c;所以说&#xff0c;只维护堆顶。它使用less&#xff08;&#xff…

鸿蒙Android4个脚有脚线

效果 min:number122max:number150Row(){Stack(){// 底Text().border({width:2,color:$r(app.color.yellow)}).height(this.max).aspectRatio(1)// 长Text().backgroundColor($r(app.color.white)).height(this.max).width(this.min)// 宽Text().backgroundColor($r(app.color.w…

2025年总结zabbix手动部署过程!

1.下载软件包。 wget https://repo.zabbix.com/zabbix/6.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_latest_6.0ubuntu22.04_all.deb dpkg -i zabbix-release_latest_6.0ubuntu22.04_all.deb apt update apt install zabbix-server-mysql zabbix-frontend-php zabbix…

3.3.2 用仿真图实现点灯效果

文章目录 文章介绍Keil生成.hex代码Proteus仿真图中导入.hex代码文件开始仿真 文章介绍 点灯之前需要准备好仿真图keil代码 仿真图参考前文&#xff1a;3.3.2 Proteus第一个仿真图 keil安装参考前文&#xff1a;3.1.2 Keil4安装教程 keil新建第一个项目参考前文&#xff1a;3.1…

TypeError: Cannot read properties of undefined (reading ‘xxx‘)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…

Spring 无法解决循环依赖的 5 种场景

一、构造器注入引发的循环依赖 1. 问题复现 Component public class ServiceA {private final ServiceB serviceB;Autowiredpublic ServiceA(ServiceB serviceB) { // 构造器注入this.serviceB serviceB;} }Component public class ServiceB {private final ServiceA servic…

Vue项目通过内嵌iframe访问另一个vue页面,获取token适配后端鉴权(以内嵌若依项目举例)

1. 改造子Vue项目进行适配(ruoyi举例) (1) 在路由文件添加需要被外链的vue页面配置 // 若依项目的话是 router/index.js文件 {path: /contrast,component: () > import(/views/contrast/index),hidden: true },(2) 开放白名单 // 若依项目的话是 permission.js 文件 cons…

案例1_2:点亮8个灯【改进版】

文章目录 文章介绍改进的原理图改进的代码效果图 文章介绍 改进的原理图 使用标号简化连线 改进的代码 #include <reg51.h> // 包含头文件void main() {// 让 LED1-LED4 低电平&#xff08;点亮&#xff09;// P0 1111 0000;P0 0xF0;while (1); // 让程序一直运行…

Bazel搭建CUDA工程入门

环境版本&#xff1a; 工程目录&#xff1a; 测试输出&#xff1a; WORKSPACE 参考仓库&#xff1a;CUDA rules for Bazel 及 examples load("bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")http_archive(name "rules_cuda…

深入理解 C 语言函数的定义

在 C 语言的编程世界里&#xff0c;函数是构建复杂程序的基石。理解函数的定义与运用&#xff0c;对于编写高效、可维护的代码至关重要。​ 函数定义的基本概念​ 函数是一组执行特定任务的代码块。它将一个复杂的问题分解为一个个小的、可管理的部分&#xff0c;提高了代码的…

从毕达哥拉斯定理到向量距离和夹角的计算

1 毕达哥拉斯定理和余弦定理 1.1 毕达哥拉斯定理&#xff08;勾股定理&#xff09; 对于 毕达哥拉斯定理&#xff08;勾股定理&#xff09; 大家应该都比较熟悉&#xff0c;在一个直角三角形中&#xff0c;两条 直角边的平方之和 等于 斜边的平方 例如一个直角三角形两个直角…

结合rpart包的决策树介绍

决策树与CART算法 决策树是一种基于树状结构的监督学习算法。它通过从根节点开始递归地对特征进行划分&#xff0c;构建出一棵树来进行决策。决策树的构建过程需要解决的重要问题有三个&#xff1a;如何选择自变量、如何选择分割点、确定停止划分的条件。解决这些问题是希望随…

Manus AI : Agent 元年开启.pdf

Manus AI : Agent 元年开启.pdf 是由华泰证券出品的一份调研报告&#xff0c;共计23页。报告详细介绍了Manus AI 及 Agent&#xff0c;主要包括Manus AI 的功能、优势、技术能力&#xff0c;Agent 的概念、架构、应用场景&#xff0c;以及 AI Agent 的类型和相关案例&#xff0…

Centos的ElasticSearch安装教程

由于我们是用于校园学习&#xff0c;所以最好是关闭防火墙 systemctl stop firewalld systemctl disable firewalld 个人喜欢安装在opt临时目录&#xff0c;大家可以随意 在opt目录下创建一个es-standonely-docker目录 mkdir es-standonely-docker 进入目录编辑yml文件 se…

Geo3D建筑材质切换+屋顶纹理

一、简介 基于Threejs开发封装建筑渲染管线&#xff0c;利用简单二维建筑矢量面轮廓程序化生成3D建筑&#xff0c;支持材质一键切换&#xff0c;支持多样化建筑墙面材质和屋顶材质&#xff0c;支持建筑透明&#xff0c;支持地形高程适配&#xff0c;支持按空间范围裁剪挖洞等。…

【移动WEB开发】流式布局

目录 1. 移动端基础 1.1 浏览器现状 1.2 手机屏幕现状 1.3 常见移动端屏幕尺寸 1.4 移动端调试方法 2. 视口 3. 二倍图 3.1 物理像素&物理像素比 3.2 多倍图 3.3 背景缩放 3.4 多倍图切图cutterman 4. 移动端开发选择 4.1 主流方案 4.2 单独制作 4.3 响应式…