【数据结构】排序——快速排序

前言

本篇博客我们继续介绍一种排序——快速排序,让我们看看快速排序是怎么实现的

💓 个人主页:小张同学zkf

⏩ 文章专栏:数据结构

若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章 ​

目录

1.快速排序(hoare方法)

2.快速排序(挖坑法)

3.快速排序(前后指针法)

 4.快速排序(非递归法)

5.快速排序特性


 

1.快速排序(hoare方法)

快速排序是 Hoare 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为: 任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
// 假设按照升序对 array 数组中 [left, right) 区间中的元素进行排序
void QuickSort ( int array [], int left , int right )
{
if ( right - left <= 1 )
return ;
// 按照基准值对 array 数组的 [left, right) 区间中的元素进行划分
int div = partion ( array , left , right );
// 划分成功后以 div 为边界形成了左右两部分 [left, div) [div+1, right)
// 递归排 [left, div)
QuickSort ( array , left , div );
// 递归排 [div+1, right)
QuickSort ( array , div + 1 , right );
}

 我们先看看快速排序的动图

整体思想,以左面的数据为key,然后先让right指针向右走,找比key位置上的值小的值,找到之后,停止移动,然后left指针向左移动找比key大的值,找到之后,交换left和right位置上的值,然后右指针继续找小,左指针继续找大,找到之后继续交换,重归此过程,直到左指针与右指针相遇,相遇的位置与key位置上的值交换,再把key赋值成相遇的位置。这是单趟排序。再将以key为中心分成两个左右区间再次递归到这个函数中,不断递归,直到最后的区间为1,或不存在区间。递归返回。

代码如下

但如果我们想让快排效率高,我们得考虑些极端情况,比如如果右边指针一直没找到比最左边的数大的,左右指针直接在key位置上相遇了。 递归只有一个区间一直递归,就会大大降低了快排的效率,特别是在有序的情况下,所以,只有每次递归,key都在中间位置时,效率才最快,所以我们可以定义一个三数取中的函数,函数的返回值与left位置上的值交换就ok了。

那三数取中么写,其实很简单,就是比较最左边最右边以及最中间的值,谁是第二大的,返回第二大的就行。

三数取中代码如下

int sanshuquzhong(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[right] > a[left]){return left;}else{return right;}}}else{if (a[mid] < a[right]){return mid;}else{if (a[right] > a[left]){return right;}else{return left;}}}
}

有了三数取中,快排效率就明显提高,但是还是有人觉得快排不够快,确实,随着递归的深入,效率会越来越慢,所以为了加快效率,我们可以进行小区间优化

 

我们由图分析可知最后一次递归耗费次数最多,所以我们可以对最后几次小区间下手,用其他排序替换快排,从而让效率提高,我们可以在最后几个区间时用插入排序来进行

void charupaixu(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (a[end] > tmp){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}

ok,到这里我们的代码就写完了,我们想一个问题,为什么我们要选key,并且选的key在左边时,一定要右边指针先走才行,为什么这么规定那。如下图分析

 

这样快速排序(hoare方法)就初步得成,所有代码如下

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void swap(int* as, int* ak)
{int tmp = *as;*as = *ak;*ak = tmp;
}
void charupaixu(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (a[end] > tmp){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}
int sanshuquzhong(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[right] > a[left]){return left;}else{return right;}}}else{if (a[mid] < a[right]){return mid;}else{if (a[right] > a[left]){return right;}else{return left;}}}
}
void kuaisupaixu(int* arr, int left,int right)
{if (right <= left){return;}if (right - left + 1 < 10)//小区间排序{charupaixu(arr + left, right -left+ 1);}int mid = sanshuquzhong(arr,left, right);//三数取中swap(&arr[mid], &arr[left]);int key = left;int begin = left;int end = right;while (begin<end){while (begin<end&&arr[end] >=arr[key]){end--;}while (begin<end&&arr[begin] <= arr[key]){begin++;}swap(&arr[end], &arr[begin]);}swap(&arr[begin], &arr[key]);key = begin;kuaisupaixu(arr,left,key-1);kuaisupaixu(arr,key+1,right);
}

 


2.快速排序(挖坑法)

随着快排的不断发展,人们优化了hoare方法,用挖坑法,虽然这种方法没有效率的提升,不过方便了人们对代码的理解再也不用考虑为什么要右边先走的问题

我们看一下这个方法的动图

 

其实就是把交换换成填补,定义一个临时变量为坑,最后把Key自然放进坑位就行,这个方法更方便我们理解

就是在hoare方法代码中微调一下就行

代码如下

// 快速排序挖坑法
void PartSort2(int* a, int left, int right)
{if (left >= right){return;}if (right - left + 1 < 10){charu(a+left, right - left + 1);}else{int mid = sanshuquzhong(a, left, right);swap(&a[mid], &a[left]);int key = a[left];int begin = left;int end = right;int keng = left;while (begin < end){while (begin < end && a[end] >= key){end--;}a[keng] = a[end];keng = end;while (begin < end && a[begin] <= key){begin++;}a[keng] = a[begin];keng = begin;}a[begin] = key;PartSort2(a, left, begin- 1);PartSort2(a, begin + 1, right);}}

3.快速排序(前后指针法)

快速排序还有另一种方法,也是最容易记住的,我们可以通过定义两个指针,刚开始一个指向key,一个指向key的下一个数,让前面那个指针一直向前走找比key小的数,第二个若找到比key小的数,那么前后指针之间的数就是比key大的数,++后指针再交换俩指针指向的数,前指针继续向前找,直到超过边界停止,最后key与此时后指针指向的书交换,并且key赋值于后指针的位置,递归key前key后空间

动图如下

我们可以画图分析一下 

 

 

代码如下

// 快速排序前后指针法
void PartSort3(int* a, int left, int right)
{if (left >= right){return;}if (right - left + 1 < 10){charu(a + left, right - left + 1);}else{int mid = sanshuquzhong(a, left, right);swap(&a[mid], &a[left]);int key = left;int man = left;int kuai = left + 1;while (kuai <= right){if (a[kuai] < a[key] && ++man != kuai){swap(&a[man], &a[kuai]);}kuai++;}swap(&a[key], &a[man]);key = man;PartSort3(a, left, key - 1);PartSort3(a, key + 1, right);}
}

 4.快速排序(非递归法)

前三种方法都是递归法,若不用递归我们该怎么弄,不用递归,我们就得需要栈这个结构,代码整体不变,把最后递归的部分改成把key左右两个区间全入栈,先右区间入栈再左区间入栈,因为栈是后进先出原则,出栈就是左区间先出栈,直到栈空,入栈的条件左指针小于Key-1,右指针大于key+1。

画图看一下

 

区间边界值入栈,来替代了递归 

代码如下

#include "stack.h"
int yici(int* a,int left,int right)
{int mid = sanshuquzhong(a, left, right);swap(&a[mid], &a[left]);int key = left;int begin = left;int end = right;while (begin < end){while (begin < end && a[end] >= a[key]){end--;}while (begin < end && a[begin] <= a[key]){begin++;}swap(&a[begin], &a[end]);}swap(&a[key], &a[begin]);key = begin;return key;
}
void QuickSortNonR(int* a, int left, int right)
{if (right - left + 1 < 10){charu(a + left, right - left + 1);}else{Stack as;StackInit(&as);StackPush(&as, right);StackPush(&as, left);while (!StackEmpty(&as)){int begin1 = StackTop(&as);StackPop(&as);int end1 = StackTop(&as);StackPop(&as);int key = yici(a, begin1, end1);if (key + 1 < end1){StackPush(&as, end1);StackPush(&as, key + 1);}if (key - 1 > begin1){StackPush(&as, key - 1);StackPush(&as, begin1);}}StackDestroy(&as);}
}

5.快速排序特性

快速排序的特性总结:
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫 快速 排序
2. 时间复杂度: O(N*logN)

3. 空间复杂度: O(logN)
4. 稳定性:不稳定

结束语 

快排有关知识就总结完了,我认为快速排序这个排序还是蛮重要的,大家要对这个排序更加重视,最后一个排序就是归并排序了,留在下篇博客说

0K,本篇博客结束!!!

 

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

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

相关文章

前端JS特效第30波:jquery图片列表按顺序分类排列图片组效果

jquery图片列表按顺序分类排列图片组效果&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> &…

Profibus协议转Profinet协议网关模块连接智能电表通讯案例

一、背景 在工业自动化领域&#xff0c;Profibus协议和Profinet协议是两种常见的工业通讯协议&#xff0c;而连接智能电表需要用到这两种协议之间的网关模块。本文将通过一个实际案例&#xff0c;详细介绍如何使用Profibus转Profinet模块&#xff08;XD-PNPBM20&#xff09;实…

vue2 实现原生 WebSocket

原生WebSocket&#xff1a; new WebSocket WebSocket | ThinkTS官网 export default {data() {return {socket: null};},created() {// 1. 创建 WebSocket 实例this.socket new WebSocket(ws://localhost:3000);// 2. 监听 WebSocket 连接打开事件this.socket.onopen () &g…

MySQL常见的几种索引类型及对应的应用场景

MySQL 提供了多种索引类型&#xff0c;每种索引类型都有其特定的应用场景和优势。以下是 MySQL 中常见的几种索引类型及其具体应用场景&#xff1a; 1. B-Tree 索引 特点&#xff1a; B-Tree&#xff08;Balanced Tree&#xff0c;平衡树&#xff09;是 MySQL 的默认索引类型…

启航IT之旅:为新生绘制的学习路线图

随着七月的热浪悄悄席卷而来&#xff0c;各地高考成绩陆续放榜&#xff0c;对于刚迈过高考这座独木桥的你们&#xff0c;这不仅仅是一个故事的终章&#xff0c;更是另一段冒险的序曲。特别是那些心中有一团IT火焰燃烧的少年们&#xff0c;暑假的钟声已经敲响&#xff0c;是时候…

坑3.上传图片(阿里云空间,oss验证)(未验证)

笔记 20240710 未验证&#xff0c;现在还没有阿里云空间&#xff0c;等买个sit环境就可以验证一下。 前端 页面 <!--页面--> <el-form-item label"优惠券图片" prop"couponImg"><single-upload v-model"dataForm.couponImg"&g…

2493-04A-6 同轴连接器

型号简介 2493-04A-6是Southwest Microwave的连接器。该连接器是一种端子连接器&#xff0c;采用 1.0 毫米插头&#xff08;公头&#xff09;进行连接。它由多个部件组成&#xff0c;包括过渡块、接地板、螺纹夹紧板、发射针、冷板、底座、电路板和外壳等。 型号特点 外壳&…

【数据结构】深入理解哈希及其底层数据结构

目录 一、unordered系列关联式容器 二、底层结构 2.1 哈希的概念 2.2 哈希冲突&#xff08;哈希碰撞&#xff09; 2.3 哈希函数 2.4 哈希冲突处理 2.4.1 闭散列&#xff08;开放定址法&#xff09; 2.4.1.1 代码实现&#xff1a; 2.4.2 开散列&#xff08;链地址法&…

Visual Studio 2022 安装及使用

一、下载及安装 VS 官网&#xff1a;Visual Studio: IDE and Code Editor for Software Developers and Teams 下载免费的社区版 得到一个.exe文件 右键安装 选择C开发&#xff0c;并修改安装位置 等待安装 点击启动 二、VS的使用 1.创建项目 打开VS&#xff0c;点击创建新项…

Python爬虫教程第6篇-使用session发起请求

为什么要使用session 前面介绍了如何使用reqesuts发起请求&#xff0c;今天介绍如何使用session发起请求。session简单理解就是一种会话机制&#xff0c;在浏览器中我们登录完之后&#xff0c;后面再请求服务数据都不需要再登录了&#xff0c;以为Cookie里已经保存了你的会话状…

【Cesium开发实战】火灾疏散功能的实现,可设置火源点、疏散路径、疏散人数

Cesium有很多很强大的功能&#xff0c;可以在地球上实现很多炫酷的3D效果。今天给大家分享一个可自定义的火灾疏散人群的功能。 1.话不多说&#xff0c;先展示 火灾疏散模拟 2.设计思路 根据项目需求要求&#xff0c;可设置火源点、绘制逃生路线、可设置逃生人数。所以点击火…

荷兰花海元宇宙的探索

在数字科技日新月异的今天&#xff0c;我们有幸见证了一个全新的概念——元宇宙的诞生。元宇宙是一个虚拟世界&#xff0c;它通过高科技手段&#xff0c;将现实世界的各种元素和场景数字化&#xff0c;使人们能够在虚拟世界中体验现实世界的生活。而在这个虚拟世界中&#xff0…

java设计模式(十六)职责链模式(Chain of Responsibility Pattern)

1、模式介绍&#xff1a; 职责链模式是一种行为设计模式&#xff0c;其中多个对象按顺序处理请求&#xff0c;直到其中一个对象能够处理请求为止。请求沿着链传递&#xff0c;直到有一个对象处理它为止。 2、应用场景&#xff1a; 职责链模式适用于以下场景&#xff1a;请求…

初学51单片机之UART串口通信

CSDN其他博主的博文&#xff08;自用&#xff09;嵌入式学习笔记9-51单片机UART串口通信_51uart串口通讯-CSDN博客 CSDN其他博主的博文写的蛮好&#xff0c;如果你想了解51单片机UART串口可以点进去看看&#xff1a; UART全称Universal Asynchronous Receiver/Transmitter即通…

常用知识碎片 分页组件的使用(arco-design组件库)

目录 分页组件使用 API 组件代码示例 使用思路&#xff1a; 前端示例代码 html script 后端示例代码 Controller Impl xml 总结 分页组件使用 使用Arco Design之前需要配置好搭建前端环境可以看我另外一篇文章&#xff1a; 手把手教你 创建Vue项目并引入Arco Desi…

生存人数00

题目链接 生存人数 题目描述 注意点 假设所有人都出生于 1900 年至 2000 年&#xff08;含 1900 和 2000 &#xff09;之间birth[i] < death[i]如果有多个年份生存人数相同且均为最大值&#xff0c;输出其中最小的年份 解答思路 初始想到的是遍历birth&#xff0c;更新…

正则表达式语法

普通字符&#xff1a; 匹配 "a" 将匹配文本中的 "a" 字符: 示例: a 匹配 "a" 元字符&#xff1a; \d 匹配 任意数字字符 等价于 [0-9] : 示例: \d 匹配 "1" \D 匹配一个非数字字符 等价于 [^0-9] \w 匹配 任意字母数字字符: 示例:…

B2B领域的客户裂变策略:打造行业内的共赢生态

在日益竞争激烈的B2B市场中&#xff0c;客户裂变作为一种高效的增长策略&#xff0c;不仅能够帮助企业快速扩大客户基础&#xff0c;还能促进行业内资源共享与合作&#xff0c;共同构建一个健康、可持续的共赢生态。本文将探讨B2B领域实施客户裂变策略的关键要素&#xff0c;以…

极狐GitLab 17.0 重磅发布,100+ DevSecOps功能更新来啦~【十】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab &#xff1a;https://gitlab.cn/install?channelcontent&utm_sourcecsdn 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署…