深入理解数据结构第五弹——排序(2)——快速排序

排序(1):深入了解数据结构第四弹——排序(1)——插入排序和希尔排序-CSDN博客

前言:

在前面我们已经讲过了几种排序方式,他们的效率有快有慢,今天我们来学习一种非常高效的排序方式——快速排序

目录

一、快速排序的思想

二、快速排序的递归实现

2.1 霍尔法

2.2 挖坑法

2.3 前后指针法

三、快排的非递归实现

四、完整代码示例

五、总结


一、快速排序的思想

快速排序是一种常用的排序算法,属于比较排序的一种。它的基本思想是先选取一个基准数据,经过一趟排序,让比它小的分为一部分,比它大的分为另一部分,然后再对这两部分继续这种操作,直到他们有序

快速排序的具体步骤如下:

  1. 选择一个基准元素(通常是待排序数组的第一个元素、最后一个元素或者中间元素)。
  2. 将比基准元素小的元素放在基准元素的左边,比基准元素大的元素放在基准元素的右边,这一步称为分区操作。
  3. 对基准元素左右两部分分别递归地进行快速排序。

比如这样一组数据{ 4,7,1,9,3,6,5,8,3,2,0 }

1、首先我们先选择一个基准元素(我们以最左边的元素为基准元素为例)

2、对剩下的元素进行排序,比基准元素小的排在左边,比基准元素大的排在右边

3、对小的部分和大的部分重复上面两部操作,最后我们就可以得到一个有序的数组

这一步就可以清楚的看到其实快排的这种思想很像二叉树,所以很容易通过类似二叉树递归的那种思想来解决

二、快速排序的递归实现
 

快排的实现其实是很有意思的,在上面我们已经讲了快排的思想,其实就是不断的重复分区操作的过程,所以我们就可以设计一个递归来实现这种,同时,由于每一步都要进行分区,所以我们可以封装一个分区排序函数(PartSort函数)在前,重复这个过程

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

其中参数a是数组指针,begin是传入数组的首元素位置,end是传入元素尾元素位置,过程图如下:

快排函数的主体就是上面那几步,接下来,我们重点讲解一下快排分区排序函数(PartSort函数)该如何实现,这一步也是非常有趣的,目前我们有三种方法来实现这个函数的功能:

      1、霍尔排序

      2、挖坑法

      3、前后指针法

2.1 霍尔法

霍尔法是霍尔大佬(就是快排的发明者)自己刚开始用的排序方法,但是由于这种分部排序方法需要注意到的点太多,所以后来才又有了后面两种排序方法,现在我们先来学习一下霍尔大佬的这种方法

霍尔排序其实就是严格按照我们上面讲的快排的那种思想进行的,就是先选一个基准数,然后对后面数进行大致的判断,让比基准数小的位于基准数左侧,比基准数大的位于基准数右侧

霍尔实现这个过程的方法就是先选取最左边的元素作为基准元素,然后记录剩下元素左右位置,然后让左边向右移动,当遇到一个比基准元素大的数就停下来,右边向左移动,遇到一个小于基准元素的数停下来,然后让左右这两个数交换

然后再讲左右两部分分开再进行类似的操作

由图可见这是一种类似二叉树的操作,所以非常适合用递归来解决,具体代码如下:

int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] > a[mid]){if (a[right] > a[left])return left;else if (a[right] < a[mid])return mid;elsereturn right;}else{if (a[right] < a[left])return left;else if (a[right] > a[mid])return mid;elsereturn right;}
}
//1、hero 霍尔排序
//[left,right]
int PartSort(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int keyi = left;while (left < right){while (left < right && a[left] <= a[keyi]){left++;}while (left < right && a[right] >= a[keyi]){right--;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);return left;
}

但前面我们也已经提到过了,霍尔排序有一些细节是一定要处理到位的,就比如

1、如果取左边数为基准元素,右边就要先开始移动(right - -),反之就左边先开始移动,否则就容易可能会出现溢出的现象

2、要注意当左右移动到的那个元素等于基准元素时是要跳过的,同时最后left==right时要将这个元素与基准元素交换

3、如果对于一个较为有序的函数,比如{1,2,4,5,7,3,9,8},快排的效率其实是偏低的,因为我们刚开始选的基准元素基本没啥作用,所以我们选择的基准元素要尽可能贴近中间值,所以就有了上述代码中的GetMid函数
 

2.2 挖坑法

鉴于霍尔法注意事项太多,且霍尔法较难理解,后面又有大佬总结出挖坑法这一思路相同,但更形象更容易理解的方法

挖坑法的思路如下:

先以左边元素为基准元素,然后将这个元素挖出,将这个位置理想化成一个坑,然后再从右边向左边移动,找到一个小于基准数的数后将它放入坑中,将这个位置作为新的坑,再从左边往右边去,找到一个大于基准数字的数,填入坑中,将这个位置作为新坑,直到最后将基准数字放入最后的坑中

挖坑法的思路要比霍尔法,简单很多,实现如下:

//2、挖坑法
int PartSort2(int* a,int left,int right)
{int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = a[left];int hole = left;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 left;
}

2.3 前后指针法

前后指针法是一个更容易理解的很不错的方法,体现了后人的智慧

前后指针法的思路如下:

首先先将最左边元素作为基准元素,然后定义一个prev表示后指针,定义一个cur表示前指针,cur=prev+1,然后让前指针先走,当遇到一个小于基准元素的数时停下来,然后让后指针走一步,然后交换这两个数据,直到前指针把所有数据走完

实现上述过程的代码:

//3、前后指针法
int PartSort3(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int prev = left;int cur = prev + 1;int keyi = left;while (cur <= right){if (a[cur] < a[keyi]){prev++;Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[prev], &a[keyi]);return prev;
}

三、快排的非递归实现

这个由于篇幅问题,留在下一章进行讲解(其实是本人累了.......坐在这写一下午了,呜呜呜呜呜........),这个明天写,嘿嘿

四、完整代码示例

上面我们就已经把快排的几种分部处理的方法和思想讲的很清楚了,接下来,我们就通过一个完整的代码实例来感受快排的魅力所在

对数组{ 4,7,1,9,3,6,5,8,3,2,0 }进行快排

Seqlish.h

//快速排序
void QuickSort(int* a, int begin, int end);

Seqlish.c

//快速排序
void PrintArray(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}
void Swap(int* e1, int* e2)
{int tmp = *e1;*e1 = *e2;*e2 = tmp;
}
int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] > a[mid]){if (a[right] > a[left])return left;else if (a[right] < a[mid])return mid;elsereturn right;}else{if (a[right] < a[left])return left;else if (a[right] > a[mid])return mid;elsereturn right;}
}
//1、hero 霍尔排序
//[left,right]
int PartSort(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int keyi = left;while (left < right){while (left < right && a[left] <= a[keyi]){left++;}while (left < right && a[right] >= a[keyi]){right--;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);return left;
}
//2、挖坑法
int PartSort2(int* a,int left,int right)
{int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = a[left];int hole = left;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 left;
}
//3、前后指针法
int PartSort3(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int prev = left;int cur = prev + 1;int keyi = left;while (cur <= right){if (a[cur] < a[keyi]){prev++;Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[prev], &a[keyi]);return prev;
}
//递归的快速排序
void QuickSort(int* a, int begin,int end)
{if (begin >= end){return;}int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

test.c

//测试快速排序
void TestQuick()
{int a[] = { 4,7,1,9,3,6,5,8,3,2,0 };PrintArray(a, sizeof(a) / sizeof(a[0]));//QuickSort(a, 0, sizeof(a) / sizeof(a[0]) - 1);      //递归快排QuickSortNonR(a, 0, sizeof(a) / sizeof(a[0]) - 1);    //非递归快排PrintArray(a, sizeof(a) / sizeof(a[0]));
}int main()
{TestQuick();return 0;
}

运行结果如下:

五、总结

总之,快排的思路是有点类似二叉树的,所以适合用递归来解决,当然,用非递归同样能处理,这里我们留下了一个尾巴,等下次解决,上面提到的这些内容,如果有不理解的地方欢迎私信与我交流或者在评论区中指出

谢谢各位大佬观看,创作不易,还请各位大佬点赞支持一下!!!

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

DRF APIView源码分析

【三】APIView源码分析 【1】Response和JsonResponse的区别 &#xff08;1&#xff09;Django REST framework的Response DRF的Response类是专门为构建RESTful API设计的。 它不仅仅是一个简单的JSON响应&#xff0c;而是包含了一系列与RESTful API交互有关的功能。 内容类型…

使用go和消息队列优化投票功能

文章目录 1、优化方案与主要实现代码1.1、原系统的技术架构1.2、新系统的技术架构1.3、查看和投票接口实现1.4、数据入库MySQL协程实现1.5、路由配置1.6、启动程序入口实现 2、压测结果2.1、设置Jmeter线程组2.2、Jmeter聚合报告结果&#xff0c;支持11240/秒吞吐量2.3、Jmeter…

用于半监督的图扩散网络 笔记

1 Title Graph Neural Diffusion Networks for Semi-supervised Learning&#xff08;Wei Ye, Zexi Huang, Yunqi Hong, and Ambuj Singh&#xff09;【2022】 2 Conclusion This paper proposes a new graph neural network called GND-Nets (for Graph Neural Diffu…

【日常记录】【CSS】利用动画延迟实现复杂动画

文章目录 1、介绍2、原理3、代码4、参考链接 1、介绍 对于这个效果而言&#xff0c;最先想到的就是 监听滑块的input事件来做一些操作 ,但是会发现&#xff0c;对于某一个节点的时候&#xff0c;这个样式操作起来比较麻烦 只看这个代码的话&#xff0c;发现他用的是动画&#x…

异地组网如何安装?

【天联】是一款强大的异地组网安装工具&#xff0c;可以帮助企业实现远程设备的统一管理和协同办公。以下是【天联】可以应用的一些场景&#xff1a; 零售、收银软件应用统一管理&#xff1a;【天联】可以结合医药、餐饮、商超等零售业的收银软件&#xff0c;实现异地统一管理。…

嵌入式4-16

tftpd #include <myhead.h> #define SER_IP "192.168.125.243" //服务器IP地址 #define SER_PORT 69 //服务器端口号 #define CLI_IP "192.168.125.244" //客户端IP地址 #define CLI_PORT 8889 //客户端端…

C# 超高速高性能写日志

1、需求 需求很简单,就是在C#开发中高速写日志。比如在高并发,高流量的地方需要写日志。我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的。 2、解决方案 2.1、简单原理说明 使用列队先缓存到内存,然后我…

初步学习node.js文件模块

环境已安装好&#xff1b; 写一个read1.js如下&#xff1b; var fs require("fs"); var data ;// 创建一个流 var stream1 fs.createReadStream(test1.jsp); stream1.setEncoding(UTF8);// 绑定data事件 stream1.on(data, function(mydata) {data mydata; });/…

openGauss学习笔记-264 openGauss性能调优-TPCC性能调优测试指导-BIOS配置

文章目录 openGauss学习笔记-264 openGauss性能调优-TPCC性能调优测试指导-BIOS配置264.1 恢复BIOS出厂设置264.2 修改相关BIOS设置264.3 重启操作系统 openGauss学习笔记-264 openGauss性能调优-TPCC性能调优测试指导-BIOS配置 本章节主要介绍openGauss数据库内核基于鲲鹏服务…

25 vs code配置

1.中文语言 搜索chinese&#xff0c;安装&#xff0c;等待重新打开 2.remote ssh 安装后F1打开&#xff0c;输入adduser 输入ssh [用户名][主机ip]&#xff0c;添加主机&#xff0c;然后选择保存配置文件 如果出现管道不存在&#xff0c;设置一下 如果出问题&#xff0c;也…

IAR 使用笔记(IAR BIN大小为0异常解决)

烧写 由于芯片的内部SPI FLASH的0级BOOT 程序起到到开启JTAG SW 仿真功能&#xff0c;一旦内部SPI FLASH存储的BL0启动代码被损坏&#xff0c;芯片的JTAG 将不能被连接。所以对BL0的烧写需要谨慎&#xff0c;烧写BL0过程保证芯片不断电。 如果烧写了多备份的启动代码&#xff…

WebRTC直播间搭建记录

考虑到后续增加平台直播的可能性&#xff0c;笔记记录一下WebRTC相关. 让我们分别分析两种情况下的WebRTC连接建立过程&#xff1a; 情况一&#xff1a;AB之间可以直接通信 1.信令交换&#xff1a; 设备A和设备B首先通过信令服务器交换SDP&#xff08;Session Description Pr…

【数据结构(七)】二叉树

❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你学更多数据结构的知识 目录 1.前言2.树形结构2.1树的概念2.2常见概念2.3树的表示形式 3.二叉树3.1概念3…

嵌入式学习55-ARM4(ADC和I²C)

1、什么是ADC,模拟量和数字量有什么特点&#xff1f; ADC&#xff1a; …

每日两题 / 15. 三数之和 73. 矩阵置零(LeetCode热题100)

15. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 先确定一个数t&#xff0c;对于剩下的两个数&#xff0c;要求两数之和为t的负数 三数之和就退化成了两数之和&#xff0c;两数之和可以用双指针 先排序&#xff0c;左右两个指针&#xff0c;指向的数之和大于目标值&…

鸿蒙入门02-首次安装和配置

注&#xff1a;还没有安装编辑器&#xff08; deveco studio &#xff09;的小伙伴请看鸿蒙入门01-下载和安装-CSDN博客 首次安装配置 编辑器&#xff08; deveco studio &#xff09;安装完毕以后需要进入配置界面进行相关配置配置完毕以后才可以正常使用 环境配置&#xf…

Unity 左右折叠显示与隐藏UI的简单实现

要实现一个简单的UI左右折叠显示与隐藏&#xff0c;可以结合遮罩&#xff0c;通过代码控制UI区块的宽度和位移来实现。 具体可以按以下步骤实现&#xff1a; 1、新建一个Image组件&#xff0c;并添加精灵&#xff0c;调整大小后&#xff0c;复制一份作为该UI的父物体&#xf…

顺序表(快速上手数据结构)

在介绍ArrayList之前, 我们需要先了解List. List是一个接口,它继承于Collection接口(Collection又继承于最顶层的接口Iterable). 从数据结构的角度来看,List就是一个线性表(Linear List),即n个具有相同类型元素的有限序列, 在该序列上可以执行增删查改等操作. 注意: List是一…

Golang面试题四(GMP)

目录 1.Goroutine 定义 2.GMP 指的是什么 3.GMP模型的简介 全局队列&#xff08;Global Queue&#xff09; P的本地队列 P列表 M列表 4.有关P和M的个数问题 P的数量问题 M的数量问题 P和M何时会被创建 5.调度器P的设计策略 复⽤线程 work stealing机制 hand off…

【linux】mobaterm如何kill pycharm进程

【linux】mobaterm如何kill pycharm进程 【先赞后看养成习惯】求点赞关注收藏&#x1f600; 使用云服务器时&#xff0c;pycharm在打开状态下&#xff0c;不小心关了mobaxterm&#xff0c;然后再输入pycharm.sh就会打不开pycharm&#xff0c;显示已经打开报错&#xff1a;Com…