【数据结构】——排序篇(中)

news/2025/6/3 6:18:20

前面我们已经了解了几大排序了,那么我们今天就来再了解一下剩下的快速排序法,这是一种非常经典的方法,时间复杂度是N*logN。

在这里插入图片描述

快速排序法:

基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

我们的快速排序可以通过递归和非递归来实现,我们先来看看递归实现快排,我们的递归快排又分为三个版本,三种方法各有各的特点,我们接下来就来看看吧。

需要调用的函数代码:

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;// begin midi end 三个数选中位数if (a[begin] < a[midi]){if (a[midi] < a[end])return midi;else if (a[begin] > a[end])return begin;elsereturn end;}else // a[begin] > a[midi]{if (a[midi] > a[end])return midi;else if (a[begin] < a[end])return begin;elsereturn end;}
}

hoare版本单趟排序:

// hoare
int PartSort1(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin, right = end;int keyi = begin;while (left < right){// 右边找小while (left < right && a[right] >= a[keyi]){--right;}// 左边找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);return left;
}

这个版本的单趟排序理解起来很容易,但是我们的坑点比较多,所以难度对于我们而言还是有点挑战性,我们需要两个变量来实现,我们的left从左边数组的首元素开始走,找比keyi位置大的元素,keyi代表的就是我们数组首元素的下标,我们用right从最后一个元素开始走,找比keyi位置小的元素,找到了一个大的元素和一个小的元素就交换,但是我们的数组中可能有多个相等的元素,所以我们内嵌循环就得用<=。我们最后left和right相遇时结束,相遇位置的右边的元素大于等于keyi位置的元素,而相遇位置左边的元素都小于等于keyi位置的元素,我们最后就将left位置的元素和keyi位置的元素交换再返回left就完成了。

挖坑法单趟排序:

int PartSort2(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int key = a[begin];int hole = begin;while (begin < end){// 右边找小,填到左边的坑while (begin < end && a[end] >= key){--end;}a[hole] = a[end];hole = end;// 左边找大,填到右边的坑while (begin < end && a[begin] <= key){++begin;}a[hole] = a[begin];hole = begin;}a[hole] = key;return hole;
}

我们先将第一个数据存放在临时变量key中形成一个坑位,我们同样用到left和right,同样的用法,left从第一个元素往后走,right从最后一个元素往前走。我们的right先走,找到一个比hole下标小的元素就于hole下标的元素交换,交换之后left再走,找到一个比hole位置大的元素就与hole位置的元素交换,然后再left位置形成一个坑点。然后又是right走,交换后left走,反复进行,最后在相遇的时候就结束循环。因为我们的hole下标的值都是空的,所以在最后我们将key的数据给hole下标的坑位就可以了,最后再返回坑位的数据。具体的过程如下图所示:
在这里插入图片描述

前后指针版本单趟排序:

int PartSort3(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int keyi = begin;int prev = begin;int cur = prev + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[prev], &a[keyi]);keyi = prev;return keyi;
}

我们的前后指针版本看起来比前两个版本更加的容易,因为我们的两个指针prev指向我们数组首元素的下标,而cur是我们首元素的下一个元素的下标,我们的cur先走,如果我们指向的元素比keyi位置的元素大的话我们就cur++向前走一位,如果比keyi位置的元素小的话我们就prev++向前走一位再与cur位置的交换,cur再继续往前走知道cur>end的时候就结束循环。最后我们再将prev位置的数据给keyi位置就完成了。
在这里插入图片描述

单趟优化版本:

void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;if (end - begin + 1 <= 10){InsertSort(a + begin, end - begin + 1);}else{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin, right = end;int keyi = begin;while (left < right){// 右边找小while (left < right && a[right] >= a[keyi]){--right;}// 左边找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;// [begin, keyi-1] keyi [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}
}

单趟优化版本就是我们的区间数据不大时,我们用直接插入排序法,而数据太大的时候我们就用hoare版本的单趟排序。

函数递归实现快排:

void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = PartSort2(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

实现我们只需要调用一个版本的单趟排序在进行递归就可以了。我们代码中是用的挖坑法的单趟排序,我们就直接调用PartSort2就可以了。

递归的我们已经了解完了,那么我们就来看看非递归的怎么实现吧:

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

非递归版本的快速排序。我们需要起始位置 begin 和结束位置 end。首先,函数创建一个栈 st,用于存储待处理的子数组的起始和结束位置。将 end 和 begin 分别压入栈中,表示对整个数组进行排序。进入循环,只要栈不为空,从栈中弹出两个元素,分别赋值给 left 和 right,表示当前要处理的子数组的起始和结束位置。调用 PartSort3 函数对子数组进行分区,得到基准元素的位置 keyi。根据分区的结果,将子数组划分为 [left, keyi-1]、[keyi]、[keyi+1, right] 三个部分。如果 keyi + 1 < right,说明右侧子数组仍然有元素需要排序,将右侧子数组的起始位置 keyi + 1 和结束位置 right 压入栈中。如果 left < keyi - 1,说明左侧子数组仍然有元素需要排序,将左侧子数组的起始位置 left 和结束位置 keyi - 1 压入栈中。循环继续进行,直到栈为空,表示所有子数组都被处理完毕。最后,销毁栈 st,就完成了非递归版本的快速排序。
在这里插入图片描述

如果对大家有所帮助的话就支持一下吧!

文章来源:https://blog.csdn.net/Lehjy/article/details/134904728
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:https://dhexx.cn/news/show-5177787.html

相关文章

HarmonyOS应用开发者基础认证考试(稳过)

判断题 ​​​​​​​ 1. Web组件对于所有的网页都可以使用zoom(factor: number)方法进行缩放。错误(False) 2. 每一个自定义组件都有自己的生命周期正确(True) 3. 每调用一次router.pushUrl()方法&#xff0c;默认情况下&#xff0c;页面栈数量会加1&#xff0c;页面栈支持的…

【周报2023.12.09】

周报2023.12.09 本周开展工作下周工作计划 本周开展工作 本周开展的工作的话一共是一下几点&#xff1a; 这三点的话是紧密相连的 逻辑这边需要考虑的东西很多 点击生成照片&#xff0c;然后获取生成照片的状态点击生成照片&#xff0c;然后获取生成照片的时间&#xff0c;并…

Linux上使用独立显卡Tesla T4(测试视频压缩)

背景 将视频处理程序单独部署至K8S之外&#xff0c;使用独立GPU显卡的一台服务器上。 需事先对GPU性能做简单测试。 已通过zabbix对Linux进行了系统资源监控。 已通过PrometheusGrafana对显卡Tesla T4做了性能监控。 逐步补充&#xff0c;稍等 2023年12月6日 操作 查看当前…

Flink 本地单机/Standalone集群/YARN模式集群搭建

准备工作 本文简述Flink在Linux中安装步骤&#xff0c;和示例程序的运行。需要安装JDK1.8及以上版本。 下载地址&#xff1a;下载Flink的二进制包 点进去后&#xff0c;选择如下链接&#xff1a; 解压flink-1.10.1-bin-scala_2.12.tgz&#xff0c;我这里解压到soft目录 [ro…

SpringMvc集成开源流量监控、限流、熔断降级、负载保护组件Sentinel | 京东云技术团队

前言&#xff1a;作者查阅了Sentinel官网、51CTO、CSDN、码农家园、博客园等很多技术文章都没有很准确的springmvc集成Sentinel的示例&#xff0c;因此整理了本文&#xff0c;主要介绍SpringMvc集成Sentinel SpringMvc集成Sentinel 一、Sentinel 介绍 随着微服务的流行&…

侯捷C++八部曲(一,面向对象)

头文件和类的声明 inline inline修饰函数&#xff0c;是给编译器的一个建议&#xff0c;到底是否为inline由编译器来决定&#xff0c;inline修饰的函数在使用时是做简单的替换&#xff0c;这样就避免了一些函数栈空间的使用&#xff0c;从能提升效率。从另一种角度看&#xff…

Java TCP(一对一)聊天简易版

客户端 import java.io.*; import java.net.Socket; import java.util.Date; import javax.swing.*;public class MyClient {private JFrame jf;private JButton jBsend;private JTextArea jTAcontent;private JTextField jText;private JLabel JLcontent;private Date data;p…

YOLOv5独家原创改进:SPPF自研创新 | SPPF与感知大内核卷积UniRepLK结合,大kernel+非膨胀卷积提升感受野

💡💡💡本文自研创新改进:SPPF与感知大内核卷积UniRepLK结合,大kernel+非膨胀卷积,使SPPF增加大kernel,提升感受野,最终提升检测精度 收录 YOLOv5原创自研 https://blog.csdn.net/m0_63774211/category_12511931.html 💡💡💡全网独家首发创新(原创),…

windows建立软链 报 无法将“mklink”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

当我执行网上提供的mklink 的时候&#xff0c;出现 mklink : 无法将“mklink”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。怎么回事&#xff0c;原来&#xff0c;要在执行的签名加 cmd /c 当我执行建立软链接时&#xff0c;提示 没有足够的权限&#xff0c;要用管理…

ubuntu串口永久权限

ubuntu串口永久权限 临时打开串口权限 sudo chmod 666 /dev/ttyUSB0该方法只能临时添加访问权限&#xff0c;一次性的&#xff0c;下次拔插串口线或者开关机还需要再次赋予串口权限。 永久打开串口权限 首先查看用户组 ls -l /dev/ttyUSB0终端输出&#xff1a; crw-rw-rw…