【排序算法】快速排序(四个版本以及两种优化)含动图)

制作不易,三连支持一下吧!!!

文章目录

  • 前言
  • 一.快速排序Hoare版本实现
  • 二.快速排序挖坑法版本实现
  • 三.快速排序前后指针版本实现
  • 四.快速排序的非递归版本实现
  • 五.两种优化
  • 总结


前言

前两篇博客介绍了插入和选择排序,这篇博客我们将会介绍一个非常重要的排序算法——快速排序,它的实践价值要大远远于前两种排序。

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


一、快速排序Hoare版本实现

动图展示如下:

这里再简单解释一下:

我们一般选取最左或最后边的值作为基准值(key),如果我们选择最左边的值为key,且我们要排成升序序列,那我们就让最右边先走(为了保证相遇位置的值一定比key小),找到比key小的位置停下来,然后左边再走,找到比key大的位置停下来,两者交换,循环上述操作,直至left和right相遇,再将相遇位置的值与key交换,这样就完成了单趟排序。

key位置的元素就排到了合适的位置。 

之后递归左右区间即可。

代码实现:

void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void PrintArray(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void QuickSort(int* a, int begin, int end)
{//最小子问题,返回条件if (begin >= end)return;int key = begin;int left = begin;int right = end;while (left<right){while (left < right && a[right] >= a[key]){right--;}while (left < right && a[left] <= a[key]){left++;}Swap(&a[left], &a[right]);}Swap(&a[key], &a[left]);key = left;QuickSort(a, begin, key - 1);QuickSort(a, key + 1, end);}

 二.快速排序挖坑法版本实现

挖坑法是在hoare大佬版本的基础上为了好让大家理解而改进的一种思路,因为hoare版本如果以最左边的值做key,则必须要让右边先走,让最右边的值做key,则必须要让左边先走,否则会出现错误。 而如果是挖坑法,则无需关心这个问题。大家自然而然会按正确的方式实现。

动图演示如下: 

通俗一点解释:

我们将key位置的值保存下来,将那个位置视为一个坑位,右边先走,找到比key小的值就填到坑位中,坑位就更新到新的位置,左边再找大,再更新坑位的位置,直到left和right相遇,将原来key中的值放到坑位中就完成了单趟排序。

这样我们就避免了考虑哪边先走的问题,因为如果左边为坑,我们自然而然的就会让右边先走!

代码实现如下:

QuickSort2(int* a, int begin, int end)
{if (begin >= end)return;int left = begin, right = end;int key = a[begin];int hole = begin;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;int keyi = hole;QuickSort2(a, begin, keyi - 1);QuickSort2(a, keyi + 1, end);}

三.快速排序前后指针版本实现 

前后指针版本是这三种方法里面最值得推荐的一种,因为它实现出来非常的简单。

动图演示如下:

 

 简单解释一下:

定义两个指针prev和cur,如果啊a[cur]比key位置的值小,就让prev前进一步,并交换cur和prev位置的值,如果a[cur]比key位置的值大,就只++cur。

最后,prev和cur之间的值就是整个序列中比key位置的值大的值。prev之前的值就是比key位置的值小的值。

代码实现如下: 

QuickSort3(int* a, int begin, int end)
{if (begin >= end)return;int prev = begin, cur = begin + 1;int keyi = begin;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[prev], &a[keyi]);keyi = prev;QuickSort3(a, begin, keyi - 1);QuickSort3(a, keyi + 1, end);}

 

四.快速排序非递归法实现 

之前的三种方法虽然思路略有不同,但本质都是分治递归的思想,这种思想比较容易理解,但是递归深度过深可能会导致栈溢出,因此,我们必须掌握非递归的实现,以适用于所有场景。 

从递归改成非递归的方法一般有两种:

  • 直接改成循环——例如:斐波那契数列
  • 借助于栈或队列的结构。

我们这里就是要借助于栈的数据结构。

整体思路就是压栈,每次取出一个区间进行单趟排序,然后再入栈,如果区间不存在或只有一个元素就不再入栈,直至栈为空时,排序就完成了。 

代码实现如下: 

void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int prev = begin;int cur = begin + 1;int keyi = begin;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[keyi], &a[prev]);keyi = prev;if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi+1);}if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}STDestroy(&st);
}

 

五 两种优化(小区间优化和三数取中)

1.小区间优化

从分治的角度看,快速排序是一种二叉树结构的排序,有些大佬觉得最后几层占了整个递归次数的百分之八九十,消耗有些大,所以当区间长度小于一定数量(一般是10)时就直接用直接插入排序就好了。

也就是说最小子问题不再是区间不存在或只有一个值了,而是区间长度小于10.

if (end - begin + 1 <= 10)
InsertSort(a, end - begin + 1);

2.三数取中 

快速排序在序列有序时,效率是不高的,时间复杂度不再是O(N*logN),而是O(N^2)。

为了尽量避免出现最坏的情况,我们可以采用三数取中的方法,防止key是最大或最小的值。

//三数取中
int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[right] > a[mid]){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 left;}else {return right;}}
}

 

以hoare版本为例,加入三数取中后,就是这样: 

//快速排序Hoare版本
void QuickSort(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;//随机数作key//int randi=rand()%(right-left+1);//randi += left;//Swap(&a[randi], &a[left]);//三数取中int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]){right--;}while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}

 


总结

快速排序是实践中运用非常多的一种排序,它的价值非常高,我们应该熟练掌握快速排序的思想。

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

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

相关文章

halcon配合yolov8导出onnx模型检测物体

1.工业上多数视觉开发都是用halcon开发的&#xff0c;halcon本身也有自己的深度学习网络&#xff0c;至于halcon如果不使用本身的深度学习&#xff0c;使用其他网络导出的onnx模型怎样配合使用&#xff1f;本文基于yolov8写一个列子。 2。创建输入数据的转换代码 #region 创建输…

Nginx高可用性架构:实现负载均衡与故障转移的探索

随着网络应用的不断发展和用户访问量的增长&#xff0c;如何确保系统的高可用性、实现负载均衡以及快速响应故障转移成为了每个运维和开发团队必须面对的挑战。Nginx作为一款高性能的HTTP和反向代理服务器&#xff0c;凭借其强大的功能和灵活的配置&#xff0c;成为了实现这些目…

题目:填空练习(指向指针的指针)

题目&#xff1a;填空练习&#xff08;指向指针的指针&#xff09; There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about …

【bugfix】/usr/local/bin/docker-compose:行1: html: 没有那个文件或目录

前言 在使用 docker-compose 管理容器化应用时&#xff0c;偶尔会遇到一些意想不到的错误&#xff0c;比如当尝试运行 docker-compose 命令时&#xff0c;终端非但没有展示预期的输出&#xff0c;反而出现类似网页错误的信息。这类问题通常与 docker-compose 的安装或配置有关…

首都师范大学聘请旅美经济学家向凌云为客座教授

2024年4月17日&#xff0c;首都师范大学客座教授聘任仪式在首都师范大学资源环境与旅游学院举行。首都师范大学资源环境与旅游学院院长吕拉昌主持了仪式&#xff0c;并为旅美经济学家向凌云教授颁发了聘书。 吕拉昌院长指出&#xff0c;要贯彻教育部产学研一体化战略&#xff0…

数据库工具类

public interface DbMapper {/*** 查询数据库类型*/String queryDbType(); }<select id"queryDbType" resultType"java.lang.String">select<choose><when test"_databaseId mysql">mysql</when><when test"_d…

虚拟机Centos扩展磁盘空间

虚拟机空间&#xff1a;现sda大小20G&#xff0c;因课程需要扩容 在虚拟机扩容中&#xff0c; 新增一块硬盘 和 直接在原有硬盘基础上扩容是一样的&#xff08;只不过在原有硬盘上扩容需要关机才可以执行&#xff09;&#xff1b; 但两者都最好先做数据备份或快照&#xff0c…

夏令营复习coding 算法第一天-状态:新奇

T1:bubble冒泡排序 &#xff08;1&#xff09;思路&#xff1a;两次循环&#xff0c;外层循环从后面开始-作为一个支点&#xff0c;内层循环每次将当前需要排序的最大的那个元素一步步移动到该支点&#xff0c;最终升序排列完成 &#xff08;2&#xff09;代码: #include &l…

5、设计模式之适配器模式/原型模式

文章目录 开始之前适配器模式使用场景注意事项何时使用优缺点代码实现 原型模式深拷贝和浅拷贝 开始之前 本章节是一个系列&#xff0c;里面用的的代码实例都是连贯的。在实现某一种设计模式时&#xff0c;为了减少代码篇幅&#xff0c;前面博客出现model类&#xff08;仅限公…

【LabVIEW FPGA入门】同步C系列模块

1.同步使用循环定时器VI计时循环速率的系列模块 数字模块SAR ADC 模块多路复用模块 数字通道可以在一个时钟周期内执行。模拟通道需要多个时钟周期。 同步模拟模块的每个通道有一个 ADC&#xff0c;采集的数据在通道之间没有明显的偏差。多路复用模块使用多路复用器通过单个 A…

Docker安装并部署Gitlab,同时解决Gitlab服务经常503/500的问题

文章目录 Docker 安装Gitlab部署后需要解决的小问题1. 获取初始密码2. 解决刚安装好的Gitlab频繁出现503/500并重启的错误 Docker 安装Gitlab docker pull gitlab/gitlab-cedocker run -d -p 11443:443 -p 11090:80 -p 11022:22 \ --name gitlab \ --restart unless-stopped \…

深入理解NumPy与Pandas【numpy模块及Pandas模型使用】

二、numpy模块及Pandas模型使用 numpy模块 1.ndarray的创建 import numpy as np anp.array([1,2,3,4]) bnp.array([[1,2,3,4],[5,6,7,8]]) print(a) #[1 2 3 4] print(b) #[[1 2 3 4][5 6 7 8]] 1.1使用array()函数创建 numpy.array(object, dtype None, copy True, ord…

vue2和vue3区别

Vue 3是Vue.js框架的下一代主要版本&#xff0c;它在2020年9月正式发布。Vue 3带来了一系列的新特性和改进&#xff0c;包括性能提升、更小的打包尺寸、更好的TypeScript支持、新的组合式API等。以下是Vue 2和Vue 3之间的一些主要区别&#xff1a; 1. 性能提升&#xff1a; …

计算机存储单位

目录 是什么 存储单位的划分 存储单位转换关系表 相关疑问 存储单位转换除了位之外的存储单位转换规则是什么 为什么在编码中最小的操作单位不是位 是什么 用来度量数据存储容量的单位。 存储单位的划分 存储单位有 位&#xff08;bit&#xff0c;也称为比特&#xff…

光伏项目怎么做预算?

随着可再生能源行业的蓬勃发展&#xff0c;光伏行业也得到了扩张。许多想要加入光伏项目投资的人&#xff0c;都在为怎样为项目做预算而苦恼&#xff0c;今天我就来跟大家分析下可以怎么做。 一、了解市场需求&#xff0c;确定预算目标 在制定光伏项目预算方案之前&#xff0c…

《SpringBoot》系列文章目录

SpringBoot是由Pivotal团队提供的全新框架&#xff0c;旨在简化新Spring应用的初始搭建以及开发过程。以下是一些关于SpringBoot的详细介绍&#xff1a; 设计目的&#xff1a;SpringBoot通过特定的方式来进行配置&#xff0c;使得开发人员不再需要定义样板化的配置&#xff0c…

「贪心算法」将数组和减半的最少操作次数

力扣原题链接&#xff0c;点击跳转。 给你一个数组&#xff0c;每次可以把其中一个数减半&#xff0c;可以对同一个数多次减半。至少操作多少次&#xff0c;才能让数组的和整体减少至少一半呢&#xff1f; 我们每次都选择当前数组中最大的那个数减半&#xff0c;就能减少最多…

链路初始化和训练

一、总览 链路初始化和训练&#xff0c;由物理层进行控制&#xff0c;是一个基于硬件的过程。初始化设备的链路和端口&#xff0c;使得设备能够收发报文&#xff0c;在链路上正常通信。 在reset后由硬件自动启动完整的训练过程&#xff0c;并由LTSSM管理。 1 位锁定 训练开始…

【Vue】diff 算法

diff的时机 当组件创建时&#xff0c;以及依赖的属性或数据变化时&#xff0c;会运行一个函数&#xff0c;该函数会做两件事&#xff1a; 运行_render生成一棵新的虚拟dom树(vnode tree)&#xff0c;返回根节点运行_update&#xff0c;传入虚拟dom树的根节点&#xff0c;对新旧…

typedef定义结构体包含函数指针的巨坑

起因 尝试在c中模仿c的类&#xff0c;把成员函数放置到结构体中。显然只能放置一个结构体指针。 于是准备这么做。 错误示范 typedef struct {int id;void(*show)(Person p); }Person;void showPerson(Person p){ //.... }void init(Person * p){p->show showPerson; …