手撕快排的三种方法:让面试官对你刮目相看

快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。 


目录

💯前言

💯快速排序基础概念 

💯Hoare 版本

1.算法思路

2.代码示例

3.有关该代码的问题

3.1😮为什么right一定是比keyi值小?

 3.2😮当arr[right] == arr[keyi]时,要不要交换?

💯挖坑法

1.算法思路

2.代码示例

💯前后指针版本

1.算法思路

2.代码示例

💯时间复杂度分析

💯总结


💯前言

🌠在排序算法的领域中,快速排序是一种被广泛应用且高效的算法。它有多种实现方式,其中 Hoare 版本挖坑法前后指针版本是比较常见且具有代表性的。这些方法在实现思路和细节上各有特点,🚩深入理解它们对于掌握快速排序算法至关重要。🚩


💯快速排序基础概念 

🍏快速排序是一种基于分治策略的排序算法。它的基本思想是选择一个基准元素(pivot),将数组分为两部分,使得左边部分的元素都小于等于基准元素右边部分的元素都大于等于基准元素。然后对左右两部分分别递归地进行排序,直到整个数组有序。

  •  😬以下动画表示解释了如何在数组中找到基准元素(pivot):


💯Hoare 版本

1.算法思路

  1. Hoare 版本的快速排序首先选择一个基准元素通常是数组的第一个元素。然后设置两个指针,一个指针left从数组的左端开始向右移动,另一个指针right从数组的右端开始向左移动。
  2. left指向的元素小于基准元素时,left指针继续向右移动。当right指向的元素大于基准元素时,right指针继续向左移动。
  3. left指向的元素大于等于基准元素且right指向的元素小于等于基准元素时,交换这两个元素。
  4. 重复上述移动指针和交换元素的操作,直到leftright指针相遇。最后将基准元素与left(或right)指针指向的元素交换,此时基准元素就处于它在排序后的正确位置。

2.代码示例

int _QuickSort(int* arr, int left, int right)
{int keyi = left;++left;while (left <= right){//right:从右往左找比基准值要小的数据while (left <= right && arr[right] > arr[keyi])//要不要让arr[right] == arr[keyi],要不要交换?{right--;}//left:从左往右找比基准值要大的数据while (left <= right && arr[left] < arr[keyi]){left++;}//left和right交换if (left <= right){Swap(&arr[left++], &arr[right--]);}}//keyi 和 right交换Swap(&arr[keyi], &arr[right]);return right;
}//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//找基准值int keyi = _QuickSort(arr, left, right);//二分// [left,keyi-1 ]  keyi  [keyi+1,right]//[0,2][4,5]QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}

3.有关该代码的问题

3.1😮为什么right一定是比keyi值小?

  1.  相遇点比基准值大时
  2. 相遇点比基准值小时

 3.2😮当arr[right] == arr[keyi]时,要不要交换?

 👇当数组的元素全是一个数字时:

 时间复杂度达到了O(n^2)

一、不进行交换

如果不进行交换,即当arr[right] == arr[keyi]时不满足循环条件,跳出内层循环继续寻找其他满足条件的位置。

  • 优点:在一些情况下可以减少不必要的交换操作,尤其是当数组中存在大量重复元素时,可能会减少一些无意义的移动,提高算法的效率。
  • 缺点:可能会导致分区不够均衡,特别是当重复元素较多且集中在一侧时,可能会使快速排序退化为接近的时间复杂度O(n^2)。例如,如果所有元素都与基准值相等,那么每次分区只会减少一个元素,递归深度将接近数组的长度,效率大大降低。

二、进行交换

如果进行交换,即当arr[right] == arr[keyi]时也被视为满足条件,可以进行交换操作。

  • 优点:可以使分区更加均衡,避免出现极端情况。对于包含大量重复元素的数组,也能更好地进行分区,减少最坏情况的发生概率,保证快速排序的平均性能。
  • 缺点:可能会增加一些不必要的交换操作,当重复元素较多时,可能会进行一些多余的交换,略微降低算法的效率。

💯挖坑法

1.算法思路

  • 挖坑法首先选择一个基准元素,通常也是数组的第一个元素,并将其保存起来,这个位置就形成了一个 “坑”。
  • 同样设置两个指针left从左端开始向右移动,right从右端开始向左移动。
  • left指向的元素小于基准元素时,left指针继续向右移动。当right指向的元素大于基准元素时,right指针继续向左移动。
  • left指向的元素大于等于基准元素且right指向的元素小于等于基准元素时,将right指向的元素放入 “坑” 中,并将right所在的位置标记为新的 “坑”。这一步是为了将小于基准的元素通过填充 “坑” 的方式放在左边,大于基准的元素放在右边。
  • 重复上述操作,直到leftright指针相遇。最后将保存的基准元素放入 “坑” 中,此时基准元素就处于它在排序后的正确位置。

2.代码示例

// 挖坑法
int PartSort2(int* a, int left, int right)
{// 选取最左边的元素作为基准值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 hole;
}void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 调用分区函数找到基准值的索引int keyi = PartSort2(a, left, right);// 对基准值左边的子数组进行递归排序QuickSort(a, left, keyi - 1);// 对基准值右边的子数组进行递归排序QuickSort(a, keyi + 1, right);
}

💯前后指针版本

1.算法思路

  1. 前后指针版本首先选择一个基准元素,通常是数组的第一个元素。然后设置一个前指针prev从数组的第二个元素开始,一个后指针end从数组的最后一个元素开始。
  2. 前指针prev不断向右移动,直到找到一个大于等于基准元素的元素。后指针end不断向左移动,直到找到一个小于等于基准元素的元素。
  3. 如果前指针prev小于后指针end,则交换这两个指针指向的元素。这一步是为了将小于基准的元素放在左边,大于基准的元素放在右边。
  4. 重复上述操作,直到前指针prev和后指针end相遇。最后将基准元素与后指针end指向的元素交换,此时基准元素就处于它在排序后的正确位置。

2.代码示例

#include <iostream>
#include <algorithm>
using namespace std;// 分区函数,实现前后指针版本的划分
int partitionTwoPointers(int arr[], int low, int high) {int pivot = arr[low];int prev = low + 1;int end = high;while (prev <= end) {// 从前向后找大于等于基准的元素while (prev <= end && arr[prev] <= pivot) prev++;// 从后向前找小于等于基准的元素while (prev <= end && arr[end] >= pivot) end--;if (prev < end) swap(arr[prev], arr[end]);}swap(arr[low], arr[end]);return end;
}// 快速排序函数,使用前后指针版本的分区
void quickSortTwoPointers(int arr[], int low, int high) {if (low < high) {int pivotIndex = partitionTwoPointers(arr, low, high);// 对基准元素左边的子数组进行排序quickSortTwoPointers(arr, low, pivotIndex - 1);// 对基准元素右边的子数组进行排序quickSortTwoPointers(arr, pivotIndex + 1, high);}
}


💯时间复杂度分析

  • 最坏情况:当每次划分选取的基准元素都是当前子序列中的最大或最小元素时,划分得到的两个子序列一个为空,另一个子序列的长度为n-1。此时,快速排序退化为冒泡排序,时间复杂度为O(n^2)
  • 最好情况:每次划分都能将序列均匀地分成两个子序列,此时时间复杂度为O(nlogn)
  • 平均情况:快速排序的平均时间复杂度为O(nlogn)

💯总结

🍎快速排序的 Hoare 版本、挖坑法和前后指针版本都是基于分治思想的高效排序算法实现方式。它们在平均情况下时间复杂度都为O(nlogn),但在最坏情况下可能退化为O(n^2)。空间复杂度在平均情况下为O(logn),最坏情况下为O(n)。这些方法各有特点,在实际应用中,可以根据具体情况选择合适的版本。例如,当数组元素分布较为均匀时,三种方法都能较好地发挥作用;当数组中可能存在大量重复元素时,前后指针版本可能在某些情况下能更有效地处理。


以后我将深入研究继承、多态、模板等特性,并将默认成员函数与这些特性结合,以解决更复杂编程问题!欢迎关注我👉【A Charmer】   

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

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

相关文章

51单片机教程(五)- LED灯闪烁

1 项目分析 让输入/输出口的P1.0或P1.0~P1.7连接的LED灯闪烁。 2 技术准备 1、C语言知识点 1 运算符 1 算术运算符 #include <stdio.h>int main(){// 算术运算符int a 13;int b 6;printf("%d\n", ab); printf("%d\n", a-b); printf("%…

ceph补充介绍

SDS-ceph ceph介绍 crushmap 1、crush算法通过计算数据存储位置来确定如何存储和检索&#xff0c;授权客户端直接连接osd 2、对象通过算法被切分成数据片&#xff0c;分布在不同的osd上 3、提供很多种的bucket&#xff0c;最小的节点是osd # 结构 osd (or device) host #主…

集成ruoyi-it管理系统,遇到代码Bug

前言&#xff1a;这次ruoyi框架开发it管理系统&#xff0c;出现很多问题&#xff0c;也有学到很多东西&#xff0c;出现几个问题&#xff0c;希望下次项目不会出现或者少出现问题&#xff1b;其中还是有很多基础知识有些忘记&#xff0c;得多多复习 1&#xff1a;当写的代码没…

大模型面试-Layer normalization篇

1. Layer Norm 的计算公式写一下? 2. RMS Norm 的计算公式写一下? 3. RMS Norm 相比于 Layer Norm 有什么特点? 4. Deep Norm 思路? 5. 写一下 Deep Norm 代码实现? 6.Deep Norm 有什么优点? 7.LN 在 LLMs 中的不同位置 有什么区别么?如果有,能介绍一下区别么? 8. LLM…

【Linux第七课--基础IO】内存级文件、重定向、缓冲区、文件系统、动态库静态库

目录 引入内存级文件重新使用C文件接口 -- 对比重定向写文件读文件文件流 认识文件操作的系统接口open参数 -- flagflag的内容宏的传参方式 open关闭文件写文件读文件结论 引入文件描述符fd、对文件的理解理解一切皆文件方法集文件fd的分配规则 重定向代码的重定向输入重定向输…

手写实现call,apply,和bind方法

手写实现call&#xff0c;apply和bind方法 call&#xff0c;apply和bind方法均是改变this指向的硬绑定方法&#xff0c;要想手写实现此三方法&#xff0c;都要用到一个知识点&#xff0c;即对象调用函数时&#xff0c;this会指向这个对象&#xff08;谁调用this就指向谁&#…

Redis全系列学习基础篇之位图(bitmap)常用命令的解析

文章目录 描述常用命令及解析常用命令解析 应用场景统计不确定时间周期内用户登录情况思路分析实现 统计某一特定时间内活跃用户(登录一次即算活跃)的数量思路分析与实现 描述 bitmap是redis封装的用于针对位(bit)的操作,其特点是计算效率高&#xff0c;占用空间少,常被用来统计…

Java | Leetcode Java题解之第518题零钱兑换II

题目&#xff1a; 题解&#xff1a; class Solution {public int change(int amount, int[] coins) {int[] dp new int[amount 1];boolean[] valid new boolean[amount 1];dp[0] 1;valid[0] true;for (int coin : coins) {for (int i coin; i < amount; i) {valid[i…

Java的包、final关键字以及代码块

Java的包、final关键字以及代码块 一、包 包的作用 &#xff1a; ​ 包就是文件夹&#xff0c;用来管理各种不同功能的Java类 包名的书写规则&#xff1a; ​ 公司域名反写 包的作用&#xff0c;需要全部英文小写&#xff0c;见名知意 什么是全类名&#xff1a; ​ 包名…

【AI视频换脸整合包及教程】AI换脸新星:Rope——让换脸变得如此简单

在数字技术迅猛发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;的应用已经渗透到了我们生活的方方面面&#xff0c;从日常的语音助手到复杂的图像处理&#xff0c;无不体现着AI技术的魅力。特别是在娱乐和创意领域&#xff0c;AI技术更是展现出了惊人的潜力。其中&a…

Ubuntu下安装和配置MySQL5.7教程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 在ubuntu下安装MySQL数据库 查看操作系统版本 ​编辑 添加 MySQL APT 源 访问下载页面并下载发布包 安装发布包 安装MySQL 查看MySQL状态 开启自启动 登…

Java中常见的异常类型

1、Exception和Error有什么区别&#xff1f; 首先Exception和Error都是继承于Throwable类&#xff0c;在Java中只有Throwable类型的实例才可以被抛出&#xff08;throw&#xff09;或者捕获&#xff08;catch&#xff09;&#xff0c;它是异常处理机制的基本组成类型。 Except…

Unity的gRPC使用之实现客户端

应用背景&#xff1a;本想Unity调用C的dll库获取一些数据资源&#xff0c;但是由于自己调用的C库模块化处理的不太理想&#xff0c;众多dll之间相互依赖&#xff0c;使得在调用dll的时候&#xff0c;会忽略一些dll的缺失&#xff0c;使Unity项目报错&#xff0c;故想到了使用gR…

GPU-主板-内存-硬盘-CPU-电源分类及区别总结大全

一、背景 用了7年的笔记本&#xff0c;现在感觉它实在是扛不住了&#xff0c;中间自己缝缝补补坚持到现在&#xff0c;把机械硬盘换成了固态&#xff0c;加装了内存条。换过2次还是3次风扇&#xff08;不知道为啥坏的&#xff0c;高转速时哧哧响&#xff09;&#xff0c;换过一…

【JavaSE练习题】数组的创建和使用

数组的创建和使用 奇数位于偶数之前冒泡排序两数之和只出现一次的数字多数元素存在连续三个奇数的数组 奇数位于偶数之前 调整数组顺序使得奇数位于偶数之前。调整之后&#xff0c;不关心大小顺序。 如数组&#xff1a;[1,2,3,4,5,6] 调整后可能是&#xff1a;[1, 5, 3, 4, 2,…

使用Vue.js和Vuex构建可维护的前端应用

使用Vue.js和Vuex构建可维护的前端应用 Vue.js简介 安装Vue.js 使用npm安装 使用CDN引入 创建Vue项目 安装Vuex 初始化Vuex Store 在Vue组件中使用Store Vuex模块化 Vuex命名空间 Vuex插件 Vuex热重载 Vuex持久化状态 Vuex调试工具 Vuex的高级用法 异步Actions 中间件 Vuex的…

小新学习k8s第四天之发布管理

一、金丝雀发布&#xff08;灰度发布&#xff09; Deployment控制器支持自定义控制更新过程中的滚动节奏&#xff0c;如“暂停(pause)”或“继续(resume)”更新操作。 ①比如等待第一批新的Pod资源创建完成后立即暂停更新过程&#xff0c;此时&#xff0c;仅存在一部分新版本的…

机器人领域中的scaling law:通过复现斯坦福机器人UMI——探讨数据规模化定律(含UMI的复现关键)

前言 在24年10.26/10.27两天&#xff0c;我司七月在线举办的七月大模型机器人线下营时&#xff0c;我们带着大家一步步复现UMI「关于什么是UMI&#xff0c;详见此文&#xff1a;UMI——斯坦福刷盘机器人&#xff1a;从手持夹持器到动作预测Diffusion Policy(含代码解读)」&…

项目模块1~12总结:服务器大模块梳理

一、思维导图 二、设计思路 1、各种回调函数梳理 服务器里面包含了监听套接字和监听到的通信套接字&#xff08;新连接&#xff09;&#xff0c;我们要对这两种套接字进行设置回调函数&#xff0c;其中监听套接字里面只要设置读回调&#xff0c;通信套接字要设置5种回调&…

【django】Django REST Framework 序列化与反序列化详解

目录 1、什么是序列化和反序列化&#xff1f; 2、Django REST Framework中的序列化和反序列化 3、安装与配置&#xff08;第10章是从零开始&#xff09; 3.1 安装 3.2 配置 4、基本使用 4.1 创建序列化器 4.2 使用序列化器&#xff08;将数据序列化返回给前端&#xff…