排序算法之六:快速排序(递归)

快速排序的基本思想

快速排序是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);
}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

划分区间的常见方式

将区间按照基准值划分为左右两半部分的常见方式有三种,

  1. hoare方法
  2. 挖坑法
  3. 前后指针法 

三种方法是排key左右区间的不同,整体快排的思想是递归 

1.hoare方法

https://img-blog.csdnimg.cn/07ddcfdc56874b2a9d12f585534ac87e.gif#pic_center

1.1图示

定义left和right来找大和找小

right先走找大,left再走找小,找到交换

继续找大找小

相遇停下来,和key交换

1.2为什么相遇位置一定比key小

这里我们有一个问题:为什么相遇位置一定比key小?

因为右边先走

相遇有两种情况:

  1. right遇left -> left先走,right没有遇到比key小的,一直走,直到遇到left停下来,left存的是比key小的值
  2. left遇right -> right先走,left没有遇到比key大的,一直走,直到遇到right停下来,right存的是比key大的值
  3. 所以我们得出一个结论,左边做key,右边先走;右边做key,左边先走

如果左边有序,右边也有序,整体就有序了

那么如何让左右有序呢?

类似二叉树,递归左树和右树

第一遍排序后的left和right的范围是:[begin,keyi-1],keyi,[keyi+1,end]

然后继续递归,直到这个区间只有一个值或者不存在

1.3代码示例

//hoare方法
int PartSort1(int*a,int begin,int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin, right = end;int keyi = begin;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;return keyi;
}

2.挖坑法 

https://img-blog.csdnimg.cn/c2dde0e21f32461fb43db524559ca36d.gif#pic_center

2.1图示

right找小,left找大,right先走,找到小和坑位交换,然后left走,left找到大之后和坑位交换,交替进行直到相遇

他们一定会相遇到坑的位置

相遇之后将key的值放到坑位中,这时候key左边就是比key小的,key右边就是比key大的

2.2代码示例

我们写一个挖坑法的函数来排keyi左右的数据

先用三数取中方法得到keyi,定义一个key保存keyi的值,定义一个坑位holei先放到begin

  • 右边找小,填到左边的坑里,右边成为新的坑
  • 左边找大,填到右边的坑里,左边成为新的坑 
  • 相遇后将key放到坑里,返回坑的下标 
//挖坑法
int PartSort2(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int key = a[begin];int holei = begin;while (begin < end){//右边找小while (begin < end && a[end] <= key)--end;a[holei] = a[end];holei = end;//左边找大while (begin < end && a[begin] >= key)++begin;a[holei] = a[begin];holei = begin;}//相遇a[holei] = key;return holei;
}

3.前后指针法

https://img-blog.csdnimg.cn/8baec430614e47dfa382926553830c14.gif#pic_center

3.1图示

prev要不就是紧跟cur,要不prev和cur之间就是比key大的值

3.2代码示例

//前后指针法
int PartSort3(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int keyi = begin;int prev = begin, cur = begin + 1;while (cur <= end){//if (a[cur] < a[keyi])//{//	++prev;//	Swap(&a[prev], &a[cur]);//	++cur;//}//else//	++cur;if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[keyi], &a[prev]);keyi = prev;return keyi;
}

4.快速排序优化

  1. 三数取中法选key
  2. 递归到小的子区间时,可以考虑使用插入排序

4.1三数取中方法

这里我们的key默认取的是第一个数,但是这种情况有个弊端,不能保证key一定是那个中间值,可能是最小的,也可能是最大的

但是理想情况下,key选中间值是效率最高的,每次都是二分 

这里就有一个方法能很好的解决这个问题:三数取中

我们写一个取中函数,将中间值与begin交换,还是将key给到begin

int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;if (a[begin] < a[midi]){if (a[midi] < a[end])return midi;else if (a[begin] > a[end])return begin;elsereturn end;}else{if (a[midi] > a[end])return midi;else if (a[end] > a[begin])return begin;elsereturn end;}
}

三数取中可以排除掉最坏的情况,相对而言可以提高效率

4.2小区间优化

如果是满二叉树,最后一层占50%的节点,倒数第二层占25%,倒数第三层占12.5%

假设我们要对这五个数排序,就需要调用六次递归,这代价是非常大的

我们可以使用插入排序,插入排序对局部的适应性是很好的,所以我们在这个区间缩小的一定范围的时候就可以使用插入排序

一般选择最后三到四层,因为最后三层就占据了将就90%的递归,将最后三层的递归消除是能够明显提高效率的

剩下的区间不一定是从0开始的,也有可能是后半段,所以这里插入排序从begin开始

总代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}elsebreak;}a[end + 1] = tmp;}
}
int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;if (a[begin] < a[midi]){if (a[midi] < a[end])return midi;else if (a[begin] > a[end])return begin;elsereturn end;}else{if (a[midi] > a[end])return midi;else if (a[end] > a[begin])return begin;elsereturn end;}
}
//hoare方法
int PartSort1(int*a,int begin,int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin, right = end;int keyi = begin;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;return keyi;
}
//挖坑法
int PartSort2(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int key = a[begin];int holei = begin;while (begin < end){//右边找小while (begin < end && a[end] <= key)--end;a[holei] = a[end];holei = end;//左边找大while (begin < end && a[begin] >= key)++begin;a[holei] = a[begin];holei = begin;}//相遇a[holei] = key;return holei;
}
//前后指针法
int PartSort3(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int keyi = begin;int prev = begin, cur = begin + 1;while (cur <= end){//if (a[cur] < a[keyi])//{//	++prev;//	Swap(&a[prev], &a[cur]);//	++cur;//}//else//	++cur;if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[keyi], &a[prev]);keyi = prev;return keyi;
}
//快排
void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;if (end - begin + 1 <= 10)InsertSort(a + begin, end - begin + 1);else{//hoare法//int keyi = PratSort1(a, begin, end);//int keyi = PartSort2(a, begin, end);int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}
}
int main()
{int a[] = { 9,8,7,6,6,5,4,3,2,1,10,14,12,11,15 };int n = sizeof(a) / sizeof(a[0]);QuickSort(a, 0, n - 1);for (int i = 0; i < n; i++){printf("%d ", a[i]);}return 0;
}

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

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

相关文章

《深入理解计算机系统》学习笔记 - 第四课 - 浮点数

Floating Point 浮点数 文章目录 Floating Point 浮点数分数二进制示例能代表的数浮点数的表示方式浮点数编码规格化值规格化值编码示例 非规格化的值特殊值 示例IEEE 编码的一些特殊属性四舍五入&#xff0c;相加&#xff0c;相乘四舍五入四舍五入的模式二进制数的四舍五入 浮…

【Qt5】setWindowFlags的标志有哪些?

2023年12月9日&#xff0c;周六晚上 窗口类型&#xff1a; Widget&#xff08;0x00000000&#xff09;&#xff1a;普通窗口部件。Window&#xff08;0x00000001&#xff09;&#xff1a;标准窗口。Dialog&#xff08;0x00000002 | Window&#xff09;&#xff1a;对话框&#…

UI自动化Selenium 鼠标滑动悬停到指定元素

ActionChains执行原理 他是按照设计好的动作顺序链式执行&#xff1b; 当调用ActionChains的方法时&#xff0c;不会立即执行&#xff0c;只是将要做的动作安装顺序存放在队列中&#xff1b;当调用perform()方法时&#xff0c;队列中的方法会依次执行&#xff1b; from sele…

西南科技大学数字电子技术实验三(MSI逻辑器件设计组合逻辑电路及FPGA的实现)预习报告

一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入法完成相关公式内容,不得贴手写图片。(注意:从抽象公式直接得出结果,不得分,页数可根据内容调整) 1、4位奇偶校验器 真值表 …

C++ Qt开发:使用关联容器类

当我们谈论编程中的数据结构时&#xff0c;顺序容器是不可忽视的一个重要概念。顺序容器是一种能够按照元素添加的顺序来存储和检索数据的数据结构。它们提供了简单而直观的方式来组织和管理数据&#xff0c;为程序员提供了灵活性和性能的平衡。 Qt 中提供了丰富的容器类&…

AI:大模型技术

Prompt Prompt&#xff08;提示&#xff09;是一种在人工智能领域&#xff0c;特别是在自然语言处理和聊天机器人中常用的技术。它是一种输入&#xff0c;用于激发人工智能模型生成相应的输出。在聊天机器人中&#xff0c;用户输入的问题或请求就是提示&#xff0c;而聊天机器…

基于AidLux的工业视觉少样本缺陷检测实战应用

1. 模型转换 AIMO网站&#xff1a; http://aimo.aidlux.com/ 试用账号和密码&#xff1a; 账号&#xff1a;AIMOTC001 &#xff0c;密码&#xff1a;AIMOTC001 上传模型选择目标平台参数设置选择自动转换转换结果并下载 2. 基于AidLux的语义分割模型部署 dataset2aidlux文件…

期待一下elasticsearch还未发布的8.12版本,由lucene底层带来的大幅度提升

现在是北京时间23年12月10日。当前es最新版本还是es8.11版本。我们可以期待一下不久的将来&#xff0c;es的8.12版本看到大幅度的检索性能提升。受益于 Lucene 9.9版本&#xff0c;内核带来的大幅提升&#xff01; 此次向量检索利用底层指令fma会性能提升5%。并且还提供了向量点…

在Spring Cloud使用Hystrix核心组件,并注册到Eureka注册中心去

其实吧&#xff0c;写Spring Cloud系列&#xff0c;我有时候觉得也挺难受的&#xff0c;因为Spring Cloud的微服务启动都需要一个一个来&#xff0c;并且在IDea中也需要占用比较大的内存&#xff0c;并且我本来可以一篇写完5大核心组件的&#xff0c;但是我却分了三篇&#xff…

简单的图像分类任务全流程示例(内含代码)

以下是一个简单的示例&#xff0c;展示了如何使用 PyTorch 处理自定义图像分类数据集&#xff1a; import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoad…

erlang实现用ets做一级缓存

一、Erlang中的ETS表和DETS表 ETS表是Erlang中的一种数据结构&#xff0c;它允许我们在内存中存储数据。ETS表有许多用途&#xff0c;其中包括作为缓存的一种实现方式。ETS表的特点是它们在内存中以表的形式存储数据&#xff0c;这使得访问和操作数据非常快。 DETS表是Erlang…

【求职】外企德科-网易游戏测试面试记录

前面的话&#xff1a;本来没想写&#xff0c;但是竟然收到了一面通过的通知&#xff0c;那就来回顾一下一面&#xff0c;为终面做做准备。 这次面试基本没有做什么准备&#xff0c;本来也就是抱着试一试的心态做的笔试&#xff0c;结果笔试通过了&#xff0c;由于笔试的内容很…

LINUX-ROS集成安装MQTT库步骤注意事项

环境信息 roottitan-ubuntu1:/home/mogo/data/jp/paho.mqtt.cpp# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.5 LTS Release: 18.04 Codename: bionic 步骤 安装doxygen sudo apt install doxygen 构…

Fcopy: 基于Coke实现内网大文件分发

在工作中&#xff0c;我曾与小伙伴讨论过这样一个实际问题&#xff1a;数据制作流程产生了一份需要上线的文件&#xff0c;而线上有数十台甚至上百台机器&#xff0c;有什么朴素的办法以尽可能快的速度将文件分发到指定的机器上吗&#xff1f;根据作者已有的知识&#xff0c;分…

普冉(PUYA)单片机开发笔记(5): 配置定时器PWM输出

概述 定时器的输出通道作为 PWM 驱动是 MCU 的常用功能。 PY32F003 有一个高级定时器 TIM1 和一个通用定时器 TIM3&#xff0c;这两个定时器都可以驱动4个输出通道。现在我们就利用 TIM1 的某一个通道实现可控占空比的 PWM 输出。 原理简介 看数据手册&#xff0c;简单摘录…

激活函数数学详解以及应用场景解释

文章目录 激活函数1. Sigmoid 激活函数例子及推导过程代码 2. ReLU 激活函数例子及推导过程 3. Tanh 激活函数例子及推导过程代码 4. Softmax 激活函数例子及推导过程代码 CNN 中的卷积层工作原理卷积计算过程卷积后的输出及 ReLU 应用 激活函数 激活函数在神经网络中扮演着至…

IPSec 协议

在 TCP/IP 协议中&#xff0c;对 IP 数据包没有提供任何安全保护&#xff0c;攻击者可以通过网络嗅探、 IP 欺骗、连接截获等方法来攻击正常的 TCP/IP 通信。因此&#xff0c;通信过程中会存在以下危险&#xff1a;数据并非来自合法的发送者、数据在传输过程中被非法篡改、信息…

前端知识(十七)——入口函数和特定函数的区别

入口函数和特定函数是编程中常见的两种函数类型&#xff0c;它们在功能和使用场景上有所不同。下面我将通过Python代码示例来解释它们的区别。 1.入口函数&#xff1a;入口函数通常是一个程序或模块的起始点&#xff0c;它负责接收用户输入或外部数据&#xff0c;并启动程序的…

DM8/达梦 数据库管理员使用手册详解

1.1DM客户端存放位置 Windows&#xff1a;DM数据库安装目录中tool文件夹和bin文件夹中。 Linux&#xff1a;DM数据库安装目录中tool目录和bin目录中。 1.2DM数据库配置助手 1.2.1Windows创建数据库 打开数据库配置助手dbca 点击创建数据库实例 选择一般用途 浏览选择数据库…

图中的最长环

说在前面 &#x1f388;不知道大家对于算法的学习是一个怎样的心态呢&#xff1f;为了面试还是因为兴趣&#xff1f;不管是处于什么原因&#xff0c;算法学习需要持续保持&#xff0c;今天让我们一起来看看这一道题目————图中的最长环&#xff0c;图论题目中比较常见的环路…