快速排序【hoare版本】【挖坑法】【双指针法】(数据结构)

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

一、hoare版本

       该算法的大体框架为:假设取数组的头为key同时保存索引变量begin的值在此处,取key的另一端为索引变量end,end不断向左移动,直至处于一个小于key的值的位置,此时再让索引变量begin不断向右移动,直至处于一个大于key的值的位置,交换两个值。由此不断循环往复,最终肯定会在一个大于key值的位置处end和begin相遇,最终交换该值与key所在的位置,即可得到一个左边比key小,右边比key大的一个数组。

       由于数组是以key为基准,左右两边分别小于和大于key,所以该数组又可分为[left,key-1] , [key+1,right],然后递归调用上面的算法,左右子序列重复该过程,直至所有元素都排列在相应位置上即可。

下图为单趟快速排序的过程

程序源代码

//单趟排序
int PartSort1(int* a, int begin, int end)
{int key = a[begin];//选取左边做keyint keyindex = begin;while (begin < end){while (begin < end && a[end] >= key)//此处必须要在此判断begin<end,防止end越界{end--;}while (begin < end && a[begin] <= key){begin++;}Swap(&a[end], &a[begin]);}//此时begin == endSwap(&a[end], &a[keyindex]);//此处不能跟key交换,因为key是局部变量return begin;
}
void QuickSort(int* a, int left , int right)
{assert(a);if (left >= right){return;}int div = PartSort1(a, left, right);//[left,div - 1]  div  [div + 1 , right]QuickSort(a, left, div - 1);QuickSort(a, div + 1, right);
}

注意事项

1.若选取右边的值做key,那么一定要让左边的begin先走,这样能保证他们性欲的位置一定是一个比key大的位置...(选取左边值做key同理)

2.注意在移动begin和end的时候每次都需要判断begin<end,防止begin和end相遇之后错过,无法进行正确的排序

优化

       经过分析我们发现:快速排序的最坏情况是每次选择的基准元素都是当前子数组的最大或最小值,导致每次划分只能减少一个元素的规模。在这种情况下,算法的时间复杂度接近于O(N*N),快速排序也就没有什么优势了,因此我们要做出优化。 

       为避免选取到最大值或者最小值,出现了三数取中法。即选取三个数的中间值作为基准,就可以很好地避免这种情况。改进后的代码变为:

//三数取中法
int GetMidIndex(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;}else{return right;}}else//a[left] > a[mid]{if (a[left] < a[right]){return left;}else if (a[mid] > a[right]){return mid;}else{return right;}}}
//单趟排序
int PartSort1(int* a, int begin, int end)
{int midindex = GetMidIndex(a, begin, end);Swap(&a[midindex], &a[begin]);int key = a[begin];//选取左边做keyint keyindex = begin;while (begin < end){while (begin < end && a[end] >= key){end--;}while (begin < end && a[begin] <= key){begin++;}Swap(&a[end], &a[begin]);}//此时begin == endSwap(&a[end], &a[keyindex]);//此处不能跟key交换,因为key是局部变量return begin;
}
void QuickSort(int* a, int left , int right)
{assert(a);if (left >= right){return;}int div = PartSort1(a, left, right);//[left,div - 1]  div  [div + 1 , right]QuickSort(a, left, div - 1);QuickSort(a, div + 1, right);
}

 二、挖坑法

       挖坑法是最主流的快速排序的方法,因为其易于理解。挖坑法,顾名思义,就是要把数据挖出来,假设以最左端位置为坑,把它的值保存在一个变量key内,定义索引变量begin和end,使end不断向左边移动,直至a[end]的值小于key,把它放在坑内,然后begin再向右移动,直至a[begin]的值大于key,把它放在a[end]的坑内。由此循环往复,我们可以得到一个与上面排序相同的结果。

程序源代码

/挖坑法
int PartSort2(int* a, int begin, int end)
{int midindex = GetMidIndex(a, begin, end);Swap(&a[midindex], &a[begin]);int key = a[begin];//坑while (begin < end){while (begin < end && a[end] >= key){end--;}a[begin] = a[end];while (begin < end && a[begin] <= key){begin++;}a[end] = a[begin];}//begin == enda[begin] = key;return begin;
}
void QuickSort(int* a, int left , int right)
{assert(a);if (left >= right){return;}int div = PartSort3(a, left, right);//[left,div - 1]  div  [div + 1 , right]QuickSort(a, left, div - 1);QuickSort(a, div + 1, right);
}

注意事项

在移动begin和end的时候,同样要每次判断begin是否小于end,防止begin和end错过

三、双指针法

双指针法相对来说较难理解一点。我们要取最后一个元素为key,定义两个变量cur,prev,其中cur是数组的首元素的索引(begin),prev位于数组首元素的前一个位置,即begin - 1。算法的思想是:cur向右不断移动,直至找到a[cur] < key,++prev,然后让prev所在位置与cur所在位置的值进行交换。依次重复这个过程,我们也可以得到跟上面两种方法相同的结果。

程序源代码

//双指针法
int PartSort3(int* a, int begin, int end)
{int midindex = GetMidIndex(a, begin, end);Swap(&a[midindex], &a[begin]);int key = a[end];int cur = begin;int prev = begin - 1;while (cur <= end){while (cur<= end && a[cur] >= key)//注意!!!{cur++;}if (cur <= end){prev++;Swap(&a[cur], &a[prev]);cur++;}}Swap(&a[++prev], &a[end]);return prev;
}
void QuickSort(int* a, int left , int right)
{assert(a);if (left >= right){return;}int div = PartSort3(a, left, right);//[left,div - 1]  div  [div + 1 , right]QuickSort(a, left, div - 1);QuickSort(a, div + 1, right);
}

代码也可以换一种形式来呈现:

//双指针法
int PartSort3(int* a, int begin, int end)
{int prev = begin - 1;int cur = begin;int keyindex = end;while (cur <= end){if (a[cur] < a[keyindex] && a[cur] != a[keyindex]){Swap(&a[cur], &a[++prev]);}cur++;}Swap(&a[keyindex], &a[++prev]);return prev;
}
void QuickSort(int* a, int left , int right)
{assert(a);if (left >= right){return;}int div = PartSort3(a, left, right);//[left,div - 1]  div  [div + 1 , right]QuickSort(a, left, div - 1);QuickSort(a, div + 1, right);
}

注意事项

在初始化prev的值时,不能将其初始化为-1,要初始化为begin-1。初始化为-1,会导致子数列在进行递归时出现问题。 


今天的分享到这就结束了,欢迎持续关注,有问题可以私信交流~

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

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

相关文章

android 和 opencv 开发环境搭建

本文详细说明给android项目添加opencv库的详细步骤&#xff0c;并通过实现图片灰度化来查看配置是否成功。 下载OPENCV ANDROID SDK 到官网下载 打开 https://opencv.org/releases/ 选择android&#xff0c;下载完成后解压出下面的文件&#xff1a; 安装android sdk 和 ndk …

Dubbo使用详解

简介 Dubbo是一个高性能、轻量级的开源Java RPC框架&#xff0c;由阿里巴巴公司开发并开源。它提供了三大核心能力&#xff1a;面向接口的远程方法调用&#xff0c;智能容错和负载均衡&#xff0c;以及服务自动注册和发现。Dubbo使得应用可通过高性能的 RPC 实现服务的输出和输…

Redis相关命令详解及其原理

Redis概念 Redis&#xff0c;英文全称是remote dictionary service&#xff0c;也就是远程字典服务。这是kv存储数据库。Redis&#xff0c;包括所有的数据库&#xff0c;都是请求-回应模式&#xff0c;通俗来说就是数据库不会主动地要给前台推送数据&#xff0c;只有前台发送了…

Java研学-Maven基础

一 概述 Maven是一个跨平台的项目管理工具&#xff0c;主要用于基于 Java 平台的项目&#xff08;Maven 底层为Java&#xff09;构建、依赖包管理和项目信息管理&#xff0c;只需要运行一条简单的命令&#xff0c;就能高效的完成构建动作   Maven 能提供一种项目的依赖配置&a…

docker里Java服务执行ping命令模拟流式输出

文章目录 业务场景处理解决实现ping功能并实时返回输出实现长ping和中断请求docker容器找不到ping命令处理 业务场景 我们某市的客户&#xff0c;一直使用CS版本的信控平台&#xff0c;直接安装客户Windows server服务器上&#xff0c;主要对信号机设备进行在线管理、方案配时…

09 STM32 - PWM

9.1 PWM简介 脉冲宽度调制(Pulse Width Modulation,简称PWM)&#xff0c;是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点&#xff0c;就是对脉冲宽度的控制。 9.2 PWM波原理 如下图所示&#xff0c;使用定时器定时&#xff0c;从0开始&#x…

VSCode使用Makefile Tools插件开发C/C++程序

提起Makefile&#xff0c;可能有人会觉得它已经过时了&#xff0c;毕竟现在有比它更好的工具&#xff0c;比如CMake&#xff0c;XMake&#xff0c;Meson等等&#xff0c;但是在Linux下很多C/C源码都是直接或者间接使用Makefile文件来编译项目的&#xff0c;可以说Makefile是基石…

SQL 最大连续合格次数 最大连胜记录次数 最大连败记录次数

有这样一个问题&#xff0c;工厂中要统计某个供应商送货检验的情况&#xff0c;依照其连续合格次数&#xff0c;决定是否免检&#xff0c;不使用游标或者循环&#xff0c;如何写这个sql。 此情景也可以用于统计连胜记录等 先要学习一下 窗函数LAG&#xff0c;指的是按分组和排…

统计学-R语言-6.1

文章目录 前言参数估计的原理总体、样本和统计量点估计区间估计评价估计量的标准有效性 总体均值的区间估计一个总体均值的估计&#xff08;大样本&#xff09;一个总体均值的估计&#xff08;小样本估计&#xff09; 练习 前言 本篇文章将开始介绍参数估计的相关知识。 参数估…

[笔记]深度学习入门 基于Python的理论与实现(四)

4. 神经网络的学习 这里说的‘学习’就是指从训练数据中自动获取最优权重参数的过程。为了进行学习&#xff0c;将导入损失函数这一指标。而学习的目的就是以该损失函数为基准&#xff0c;找出能使它的值达到最小的权重参数。为此&#xff0c;我们介绍利用了函数斜率的梯度法。…

O(1)插入、删除和随机元素[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 实现RandomizedSet类&#xff1a; 【1】RandomizedSet()初始化RandomizedSet对象。 【2】bool insert(int val)当元素val不存在时&#xff0c;向集合中插入该项&#xff0c;并返回true&#xff1b;否则&#xff0c;返回false。 【3】…

【算法与数据结构】343、LeetCode整数拆分

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;博主做这道题的时候一直在思考&#xff0c;如何找到 k k k个正整数&#xff0c; k k k究竟为多少合适。…