【八大排序(四)】快排-到底多快才能追上奔驰车里的夏树?

2023-11-05 02:30

本文主要是介绍【八大排序(四)】快排-到底多快才能追上奔驰车里的夏树?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:八大排序专栏⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习排序知识
  🔝🔝


在这里插入图片描述

快速排序

  • 1. 前言🏁
  • 2. 快速排序基本思想🏁
  • 3. 快排思想实现(hoare版本)🏁
  • 4. 对hoare版本思路的解释🏁
  • 5. 单趟快排代码实现🏁
  • 6. 快排递归实现🏁
  • 7. 快排缺陷以及优化🏁
  • 8. 效率分析以及拓展🏁

1. 前言🏁

博主第一次听见这个排序的时候
只觉得它很嚣张.别人都叫:
插入,希尔,归并排序,凭什么你叫快排
你到底有多快?心里是这个表情

在这里插入图片描述
但是当我学习完快排的思想
并且用三种方法写出代码后
博主只想说这是什么神仙思想
想出此方法的人定非 常人也!

在这里插入图片描述

废话不多说,上硬菜!


2. 快速排序基本思想🏁

基本思想:

  • 从待排序的数组中选取一个基准值.
    (我们把基准值记为key)

  • 再将数组分为两部分:
    1. 左子数组所有元素小于基准值
    2. 右子数组所有元素大于基准值

  • 左右子数组再选基准值重复这个过程

对基准值选取的思考:

一般把数组第一个或
最后一个元素作为基准值
但是这样做有一定的缺陷
后面遇见问题了再修正


3. 快排思想实现(hoare版本)🏁

hoare版本:(发明者叫:C. A. R. Hoare )
也就是发明快排的人想出的方法

我们先定义一个无序数组:

int a[10]={6,1,2,7,9,3,4,5,10,8};

思路详解:

  1. 将第一个元素6作为基准值
  2. 定义两个指针指向:第一和最后的位置
  3. 左边的指针(L)找比基准值大的值
  4. 右边的指针( R)找比基准值小的值
  5. R先走.R找到满足要求的值后停下
  6. L再走,找到满足要求的值后停下
  7. 当L和R都停下,交换两个位置数据
  8. 当L和R相等时:
    交换当前位置与基准值的值.

听起来比较抽象,我们画图理解一下:

在这里插入图片描述

走完一次循环后,数组变成了这样:

在这里插入图片描述

基准值的左边全部小于它
基准值的右边全部大于它

这说明这个基准值6
已经放在了数组中的正确位置!
也就是最终排好序的位置

我们只要不断递归左右子数组
最终这个数组就会变成有序!


4. 对hoare版本思路的解释🏁

思考:

  1. l 和 r 相遇点的值会不会比key大?
    (这样交换后,key的左子序有值大于key了)
  2. 最右的元素做key的步骤是什么?

解释:

  1. 左边当基准值,右边先走

r 会寻找到比key小的值后停下来
停下来后 l 再走.当 l 和 r 都停下
并且 l 不等于 r时会交换它们的值

所以 l 和 r相遇的地方
只有两种可能

  1. r 停下的位置,值比key小
  2. 初始位置,值与key相同
    (当数组中所有值都比key大时)
    ( l 和 r会在初始位置相遇)
  1. 步骤: 右边当基准值,左边先走

步骤和左边做基准值很像
左边先走,l 找大于key的值
找到后停下来右边再走
当 l 和 r 都停下并且它们不相等时
交换 l 和 r 所在位置的值

注意不一样的来了:
当 l 和 r 相遇后:
将当前位置的下一个位置的值
与基准值交换

拓展:
上面同时也解释了为什么:
左边做key要右边先走
右边做key要左边先走


5. 单趟快排代码实现🏁

void Partion(int* a, int left, int right)
{int key = left;while (left < right){//右边先走,找小while (left < right && a[right] >= a[key]){right--;}//左边再走,找大while (left < right && a[left] <= a[key]){left++;}//左右都停下了,交换Swap(&a[left], &a[right]);}//当left和right相遇,交换此位置和key的值Swap(&a[left], &a[right]);
}

这里需要注意的是:

内层的while循环条件要加上:
left < right.不加上可能会越界


6. 快排递归实现🏁

因为实现单趟快排的函数
不好自身递归,所以我们需要
重新写一个函数来包含这个递归过程:

//快速排序
void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int key = Partion(a, left, right);QuickSort(a, left, key - 1);//递归左子序列QuickSort(a, key + 1, right);//递归右子序列
}

这里单趟快排函数Partion
需要个返回值返回l和r的相遇点

int Partion(int* a, int left, int right)
{int key = left;while (left < right){//左边为key,那么右边先走,找小while (a[right] >= a[key] && left < right){right--;}//左边后走,找大while (a[left] <= a[key] && left < right){left++;}swap(&a[left], &a[right]);}swap(&a[left], &a[key]);//将key位置和左右相遇的位置交换return left;//将左或者右的值返回去当下一次递归的头或尾
}

7. 快排缺陷以及优化🏁

快排缺陷:

当原数组已经有序时
再使用快排可能会导致栈溢出
(报错为:StackOverflow)

因为指针 r 每次都走完了全部元素
在这里插入图片描述
一共要走:N+(N-1)+(N-2)+...+1次

时间复杂度为: O(N^2)

快排面对有序数组的问题
实际上是选取的key是数组的
最大值或最小值产生的.

快排优化:

要解决快排面对有序序列
递归次数过多的问题.
实际上就是解决在有序序列
选基准值key的问题

解决方法:

三数取中法:

从最左,最右和中间的元素中
选取一个既不是最大也不是最小的
元素做基准值key.
选好后都将它与最左边的值交换
相当于还是用最左边做key.
只不过这个key不会选到最大或最小数

代码实现:

int GetMidIndex(int* a, int left, int right)//三数取中,返回中间值
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] > a[right]){return left;}else{return right;}}else // a[left] > a[mid]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]){return left;}else{return right;}}
}

写出快速排序优化后的完整版代码:

//单趟快排(hoare版本)
int Partion(int* a, int left, int right)
{//三数取中--面对有序的情况不会栈溢出(key不会选到最大或者最小的数)int mini = GetMidIndex(a, left, right);swap(&a[left], &a[mini]);//交换三数取中的值与最左边的值int key = left;//基准值还是最左边的元素while (left < right){//左边为key,那么右边先走,找小while (a[right] >= a[key] && left < right){right--;}//左边后走,找大while (a[left] <= a[key] && left < right){left++;}swap(&a[left], &a[right]);}swap(&a[left], &a[key]);//将key位置和左右相遇的位置交换return left;//将左或者右的值返回去当下一次递归的头或尾
}//快速排序
void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int key = Partion(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);
}

8. 效率分析以及拓展🏁

时间复杂度分析:

  1. 单趟次数:

单趟快排的时间复杂度比较好算:
指针 l 和 r走遍数组也就是走 n 次
时间复杂度为: O(N)

  1. 总共趟数

在理想情况下,理想情况指:
每次选基准值key都接近中位数.
这种情况下类似于二叉树拆分
一共要走 log2n趟.
时间复杂度为: O(logn)

  1. 快排总的效率:

时间复杂度为: O(N*log2N)


拓展:

既然快排有hoare版本
那肯定就有其他版本:

  1. 挖坑法
  2. 前后指针法

这都是国内的大佬想出来的办法

然而不管是hoare,挖坑,前后指针法
都是使用递归的手段解决问题

还有一种方法:
快排非递归法它可以解决栈溢出的问题

前面所有的方法都会一一给大家分享😁

快排不能使用的场景:

就算快排再快,并且做了三数取中优化
但遇见全是重复数字的数组效率也非常低!
如果你提前知道待排序序列中
很多都是重复的数字,那么
你应该避免使用快速排序.

快速排序再快
也有排不了的序列
就像:
再快的AE86也追不回
坐在奔驰车上的夏树

在这里插入图片描述


🔎 下期预告:快速排序进阶版 🔍

这篇关于【八大排序(四)】快排-到底多快才能追上奔驰车里的夏树?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/346815

相关文章

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

C++快速排序超详细讲解

《C++快速排序超详细讲解》快速排序是一种高效的排序算法,通过分治法将数组划分为两部分,递归排序,直到整个数组有序,通过代码解析和示例,详细解释了快速排序的工作原理和实现过程,需要的朋友可以参考下... 目录一、快速排序原理二、快速排序标准代码三、代码解析四、使用while循环的快速排序1.代码代码1.由快

Java中八大包装类举例详解(通俗易懂)

《Java中八大包装类举例详解(通俗易懂)》:本文主要介绍Java中的包装类,包括它们的作用、特点、用途以及如何进行装箱和拆箱,包装类还提供了许多实用方法,如转换、获取基本类型值、比较和类型检测,... 目录一、包装类(Wrapper Class)1、简要介绍2、包装类特点3、包装类用途二、装箱和拆箱1、装

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Python中lambda排序的六种方法

《Python中lambda排序的六种方法》本文主要介绍了Python中使用lambda函数进行排序的六种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录1.对单个变量进行排序2. 对多个变量进行排序3. 降序排列4. 单独降序1.对单个变量进行排序

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时