快排(快速排序)的递归与非递归实现(文末附完整代码)

快排有几种不同的写法,下面一一来介绍并实现。其中又分为递归和非递归的写法,但大体思路相同,只是代码实现略有不同。(注:文章中的完整代码中,Swap()函数均省略未写,记得自己补充)

递归写法

递归的写法类似于二叉树的前序遍历,先数组本身排序,再递归左右两个区间,直至将无序数组变为有序。

hoare版本

霍尔版本是快排的提出者,也就是最开始的写法。

其基于分治的思想,在数组中选中一个值作为基准(keyi),利用这个值(a[keyi]),进行排序,将待排序元素分为两份,比a[keyi]小的值在其左侧,比a[keyi]大的值在其右侧。此时a[keyi]这个值就排好序了,然后用递归的方法对左右两侧进行重复操作,直至无序数组变为有序。

一次排序过程如下:

其中,还有一些小问题需要注意:

代码实现为:

// 快速排序hoare版本
int PartSort(int* a, int left, int right)
{int keyi = left;int begin = left, end = right;while (begin < end){//右边找小while (begin < end && a[end] >= a[keyi]){end--;}//左边找大while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);keyi = begin;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int keyi = PartSort(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

挖坑法

挖坑法的思想是选中一个坑位(其值记为tmp,坑位的起始位置为begin),然后从右边找比tmp小的值,填入其中(此时要begin++),然后从左边找比tmp大的值填入新的坑位(此时坑位在end处,填入后要end--)。

最后begin和end相遇时,将tmp的值填入。

代码实现为:

// 挖坑法
int PartSort(int* a, int left, int right)
{int tmp = a[left];int begin = left, end = right;while (begin < end){while (begin < end && a[end] >= tmp){end--;}a[begin] = a[end];if (begin >= end){break;}begin++;while (begin < end && a[begin] <= tmp){begin++;}a[end] = a[begin];if (begin >= end){break;}end--;}a[begin] = tmp;return begin;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int keyi = PartSort(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

写的这个版本看起来有些长,原因是我在写时,没有再用一个变量来记录坑位的位置,我们可以再添加一个变量来记录坑位的位置。

// 挖坑法
int PartSort(int* a, int left, int right)
{int tmp = a[left];int begin = left, end = right;int key = begin;while (begin < end){while (begin < end && a[end] >= tmp){end--;}a[key] = a[end];key = end;while (begin < end && a[begin] <= tmp){begin++;}a[key] = a[begin];key = begin;}a[key] = tmp;return key;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int keyi = PartSort(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

一趟排序过程如下:

双指针

双指针法的中心思想是利用 prevcur 两个指针来操作。

 具体代码实现如下:

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{int keyi = left;int prev = left, cur = left + 1;while (cur <= right){//这里如果a[keyi] > a[cur],就会判断++prev != cur,//prev就会加一,只有prev和cur所在位置不同时才会发生交换。if (a[keyi] > a[cur] && ++prev != cur){Swap(&a[prev], &a[cur]);}cur++;}//最后交换prev位置和keyi位置的值Swap(&a[prev], &a[keyi]);keyi = prev;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int keyi = PartSort3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

非递归

有时候,递归深度太深会导致栈溢出,所以我们有时要用非递归来实现快排,非递归实现快排,我们就需要借助栈来实现。其思想就是模拟递归的过程,并用循环来替代,循环每走一次,就相当于递归了一次。所以要用栈来记录每次要排序的区间(也就是每次递归的 leftright )。栈的实现详解(点这里)。

// 快速排序hoare版本
int PartSort(int* a, int left, int right)
{int keyi = left;int begin = left, end = right;while (begin < end){//右边找小while (begin < end && a[end] >= a[keyi]){end--;}//左边找大while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);keyi = begin;return keyi;
}#include "Stacktest.h"// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{stack st;StackInit(&st);//先入右,再入左StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st)){int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);//排序int keyi = PartSort(a, begin, end);if (keyi + 1 < end){StackPush(&st, end);StackPush(&st, keyi + 1);}if (begin < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, begin);}}StackDestory(&st);
}

具体过程如下:

优化

快排中 keyi 的选取是十分最重要的,快排的时间复杂度为O(N*logN),但其最坏情况下为O(N^2),如果基准值是数组中最大或最小的数值,则快速排序的递归深度会非常深,排序效率会很低。若是一个有序数组使用快速排序,则递归深度为n,单趟排序也为n,此时时间复杂度为O(N^2),为了避免最坏情况的发生,我们通常是在数组中随意选择一个数作为基准。这里有几种方法:随机数法、取中间位置和三数取中法。

随机数法

随机选一个,有概率选到最大值或最小值。

#include<stdlib.h>
#include<time.h>int GetNumber(int* a, int left, int right)
{return rand() % (right - left + 1) + left;
}// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{int n = GetNumber(a, left, right);Swap(&a[left], &a[n]);int keyi = left;int begin = left, end = right;while (begin < end){//右边找小while (begin < end && a[end] >= a[keyi]){end--;}//左边找大while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);keyi = begin;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int keyi = PartSort1(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}int main()
{srand((unsigned int)time(NULL));int arr[] = { 6,5,7,9,2,0,3,1,8,4,10 };int len = sizeof(arr) / sizeof(int);QuickSort(arr, 0, len - 1);//打印数组Print(arr, len);return 0;
}

取中间位置的数

取中间元素位置,也有可能选到最大值或最小值。

​
​
#include<stdlib.h>
#include<time.h>int GetNumber(int* a, int left, int right)
{return (right + left) / 2;
}// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{int n = GetNumber(a, left, right);Swap(&a[left], &a[n]);int keyi = left;int begin = left, end = right;while (begin < end){//右边找小while (begin < end && a[end] >= a[keyi]){end--;}//左边找大while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);keyi = begin;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int keyi = PartSort1(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}int main()
{srand((unsigned int)time(NULL));int arr[] = { 6,5,7,9,2,0,3,1,8,4,10 };int len = sizeof(arr) / sizeof(int);QuickSort(arr, 0, len - 1);//打印数组Print(arr, len);return 0;
}

三数取中法

三数取中法是指比较最左边、最右边和中间元素的大小选出折中值。不会选到最大值或最小值。

​
​
​
#include<stdlib.h>
#include<time.h>int GetNumber(int* a, int left, int right)
{int mid = (right + left) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] < a[right]){return right;}else{return left;}}else //a[mid] <= a[left]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]){return left;}else{return right;}}
}// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{int n = GetNumber(a, left, right);Swap(&a[left], &a[n]);int keyi = left;int begin = left, end = right;while (begin < end){//右边找小while (begin < end && a[end] >= a[keyi]){end--;}//左边找大while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);keyi = begin;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int keyi = PartSort1(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

小区间优化

递归不断的拆分左右区间,越往深递归,所耗费的时间就越多,当递归至区间中元素个数为个位数字时,使用快排反而降低了效率,这是我们可以考虑使用插入排序来进行小区间优化。

void QuickSort(int* a, int left, int right)
{if (left >= right){return;}//小区间优化if ((right - left + 1) < 10){InsertSort(a + left, left, right);}else{int keyi = PartSort1(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
}

完整代码

#include<stdio.h>
#include<stdlib.h>
#include<time.h>void Swap(int* p1, int* p2)
{int* tmp = *p1;*p1 = *p2;*p2 = tmp;
}void Print(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}//插入排序
void InsertSort(int* a, int n)
{for (int i = 1; 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--;}else{break;}}a[end + 1] = tmp;}
}// 快速排序递归实现int GetNumber(int* a, int left, int right)
{//return (right - left) / 2;//return rand() % (right - left + 1) + left;int mid = (right + left) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] < a[right]){return right;}else{return left;}}else //a[mid] <= a[left]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]){return left;}else{return right;}}
}// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{int n = GetNumber(a, left, right);Swap(&a[left], &a[n]);int keyi = left;int begin = left, end = right;while (begin < end){//右边找小while (begin < end && a[end] >= a[keyi]){end--;}//左边找大while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);keyi = begin;return keyi;
}// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{int tmp = a[left];int begin = left, end = right;int key = begin;while (begin < end){while (begin < end && a[end] >= tmp){end--;}/*a[begin] = a[end];if (begin >= end){break;}begin++;*/a[key] = a[end];key = end;while (begin < end && a[begin] <= tmp){begin++;}/*a[end] = a[begin];if (begin >= end){break;}end--;*/a[key] = a[begin];key = begin;}a[key] = tmp;return key;
}// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{int keyi = left;int prev = left, cur = left + 1;while (cur <= right){//这里如果a[keyi] > a[cur],就会判断++prev != cur,//prev就会加一,只有prev和cur所在位置不同时才会发生交换。if (a[keyi] > a[cur] && ++prev != cur){Swap(&a[prev], &a[cur]);}cur++;}//最后交换prev位置和keyi位置的值Swap(&a[prev], &a[keyi]);keyi = prev;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}//小区间优化if ((right - left + 1) < 10){InsertSort(a + left, left, right);}else{int keyi = PartSort1(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
}#include "Stacktest.h"// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{stack st;StackInit(&st);StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st)){int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);int keyi = PartSort1(a, begin, end);if (keyi + 1 < end){StackPush(&st, end);StackPush(&st, keyi + 1);}if (begin < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, begin);}}StackDestory(&st);
}int main()
{srand((unsigned int)time(NULL));int arr[] = { 6,5,7,9,2,0,3,1,8,4,10 };int len = sizeof(arr) / sizeof(int);QuickSort(arr, 0, len - 1);Print(arr, len);return 0;
}

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

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

相关文章

glm-4v-9b 部署

glm-4v-9b 模型文件地址 GLM-4 仓库文件地址 官方测试 硬件配置和系统要求 官方测试硬件信息: OS: Ubuntu 22.04Memory: 512G…

进程概念(二)

目录 进程优先级基本概念查看系统进程PRI and NIPRI vs NI修改进程优先级的命令renice修改优先级进程其他概念 环境变量基本概念查看环境变量方法常见环境变量PATHHOMESHELL 查看环境变量环境变量相关的命令 环境变量特征命令行参数main函数中的俩个参数 argc argvmain函数的第…

LabVIEW缝缺陷图像标注库

LabVIEW缝缺陷图像标注库 开发了一个基于LabVIEW平台构建的船舶焊缝缺陷图像标注库。该库旨在通过高效和简洁的方式处理和标注船舶焊缝缺陷图像&#xff0c;提高缺陷识别的准确性和效率&#xff0c;进而保障船舶的结构安全。 项目背景 在船舶制造过程中&#xff0c;焊接质量…

人工智能和物联网如何结合

欢迎来到 Papicatch的博客 文章目录 &#x1f349;引言 &#x1f349;AI与IoT的结合方式 &#x1f348;数据处理和分析 &#x1f34d;实例 &#x1f348;边缘计算 &#x1f34d;实例 &#x1f348;自动化和自主操作 &#x1f34d;实例 &#x1f348;安全和隐私保护 &…

YOLOv10 超详细解析 | 网络结构、训练策略、论文解读

网络结构 1. Backbone 2. Head 3. 说明 网络结构按 YOLOv10m 绘制&#xff0c;不同 scale 的模型在结构上略有不同&#xff0c;而不是像 YOLOv8 一样仅调整 depth 和 width。Head 有部分后续计算与 YOLOv8 完全相同&#xff0c;上图省略&#xff0c;具体请看此文。YOLOv10 整…

Vue3+Vite报错:vite忽略.vue扩展名 Failed to resolve import ..... Does the file exist?

Vue3Vite报错&#xff1a;vite忽略.vue扩展名 Failed to resolve import … Does the file exist? 先看报错&#xff1a; 分析原因 原因是我们没有写后缀名 建议你在你的vite.config.js中加上如下配置 import { defineConfig } from "vite"; import vue from &qu…

人工智能程序员应该有什么职业素养?

人工智能程序员应该有什么职业素养&#xff1f; 面向企业需求去学习AI必备技能实战能力实战能力提升策略 面向企业需求去学习 如果想要应聘AI相关的岗位&#xff0c;就需要知道HR和管理层在招聘时需要考察些什么&#xff0c;面向招聘的需求去学习就能具备AI程序员该有的职业素…

怀庄之醉酱香白酒的历史(一)——茅台镇的地理位置与名称由来

茅台镇&#xff0c;这个黔北的历史名镇&#xff0c;自古以来就有着“川盐走贵州&#xff0c;秦商聚茅台”的繁荣景象。它坐落于仁怀市城西13公里之处的赤水河东岸&#xff0c;具体地理位置是东经10622′&#xff0c;北纬2751′。这个镇子依山傍水&#xff0c;位于寒婆岭下&…

C++中的宏定义

目录 摘要 1. 条件编译 2. 宏函数 3. 字符串化和连接 4. 可变参数宏 5. 宏和模板结合使用 6. 防止重复包含 7. 复杂宏定义 8. 安全的宏函数 9. 内联宏与内联函数的比较 总结 摘要 C中的宏定义&#xff08;Macros&#xff09;是预处理器的一部分&#xff0c;可以在编…

RJ45 PCB布线

RJ45底盘接地和数字地通过一个1M欧姆的电阻和一个0.1uF的去耦电容隔离。其底盘接地和数字地的间距&#xff0c;必须比60mil宽。如图11及图12所示。 图11 典型变压器集成单RJ45的机箱/数字地平面 图12 典型RJ45和变压器分开的机箱/数字地平面https://www.bilibili.com/read/…

主从式光伏并网发电系统体系结构

通过控制组协同开关&#xff0c;来动态滴决定在不同的外部环境下光伏并网系统的结构&#xff0c;以期望达到最佳的光伏能量利用效率。 当外部光照强度较低时&#xff0c;控制组协同开关使所有的光伏组件只和一个并网逆变器相连&#xff0c;构成为集中式结构&#xff0c;从而克…

flink 作业动态维护更新,不重启flink,不提交作业

Flink任务实时获取并更新规则_flink任务流实时变更-CSDN博客 一种动态更新flink任务配置的方法_flink 数据源 动态更新-CSDN博客 Flink CEP在实时风控场景的落地与优化 最佳实践 - 在SQL任务中使用Flink CEP - 《实时计算用户手册-v4.5.0》 Flink SQL CEP详解-CSDN博客 如…

Java——ArrayList与顺序表

一、线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列&#xff0c;线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列... 线性表在逻辑上是线性结构&#xff0c;也就是连续的一条直…

供应链经理面试题

供应链经理面试题通常会涉及对供应链管理的基本理解、工作经验、解决问题的能力以及团队协作等多个方面。 请简要介绍一下你在供应链管理领域的工作经验和取得的成绩。你如何定义供应链管理&#xff1f;它在企业中的作用是什么&#xff1f;你认为供应链经理最重要的职责是什么…

Qt无边框

最简单的可拖动对话框(大小不可改变) #ifndef DIALOG_H #define DIALOG_H/*** file dialog.h* author lpl* brief 无边框dialog类* date 2024/06/05*/ #include <QDialog> #include <QMouseEvent> namespace Ui { class Dialog; } /*** brief The Dialog class* 无…

java版知识付费saas租户平台:剖析现代知识付费平台的功能架构与运营逻辑

在数字化学习的时代背景下&#xff0c;知识付费平台已经成为教育行业的一颗璀璨明星&#xff0c;以其用户需求为中心&#xff0c;提供便捷高效的学习途径。这些平台汇聚了众多专业知识&#xff0c;覆盖职业技能、生活兴趣和人文社科等多个领域&#xff0c;满足不同用户的学习需…

RAG技术全解析:打造下一代智能问答系统

一、引言 点击可以查看最新资源 在人工智能的浪潮中&#xff0c;大型语言模型&#xff08;LLM&#xff09;凭借其强大的文本生成和理解能力&#xff0c;已经取得了显著的成果。然而&#xff0c;面对特定领域或知识密集型任务时&#xff0c;LLM仍然面临着诸多挑战&#xff0c;尤…

基于Python的AI动物识别技术研究

基于Python的AI动物识别技术研究 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat、Maven 系统功能实现 系统的登录模块设计 本次设计的AI动物识别系统为了保证用户的数据安全&#xff0c;设计了登录的模块&…

网络基础-IP协议

文章目录 前言一、IP报文二、IP报文分片重组IP分片IP分片示例MTUping 命令可以验证MTU大小Windows系统&#xff1a;Linux系统: 前言 基础不牢&#xff0c;地动山摇&#xff0c;本节我们详细介绍IP协议的内容。 一、IP报文 第一行&#xff1a; 4位版本号指定IP协议的版本&#…

C++使用thread_local实现每个线程下的单例

对于一个类&#xff0c;想要在每个线程种有且只有一个实例对象&#xff0c;且线程之间不共享该实例&#xff0c;可以按照单例模式的写法&#xff0c;同时使用C11提供的thread_local关键字实现。 在单例模式的基础上&#xff0c;使用thread_local关键字修饰单例的instance&…