【数据结构与算法】quicksort快速排序算法大全:快速排序hoare法,快速排序挖坑法,快速排序前后指针法,快速排序优化,快速排序的非递归实现

一、快速排序算法

快速排序使用分治的思想来进行排序,其基本过程如下:

  1. 从待排序数组中选择一个元素作为枢轴(pivot)。
  2. 将数组分割成两个子数组,使得左侧子数组的元素都小于等于枢轴,右侧子数组的元素都大于等于枢轴。这个分割的过程称为划分(partition)。
  3. 对左右子数组分别递归地进行快速排序。
  4. 递归结束的条件是子数组只有一个元素或者为空。

时空复杂度: 

  • 平均时间复杂度:O(nlogn)
  • 当数组有序时,最坏时间复杂度:O(n^2)
  • 空间复杂度:O(logn)

1、hoare划分法

代码实现:

void QuickSortHoare(int* a, int begin, int end)
{// begin为首元素下标,end为尾元素下标if (begin >= end){return; // 递归结束条件:子数组只有一个元素或为空,不需要排序}int left = begin; // 左指针int right = end; // 右指针int key = begin; // 选择第一个元素作为基准元素while (left < right){// 从右侧开始,找到第一个比基准元素小的元素while (a[right] >= a[key] && right > left){right--;}// 从左侧开始,找到第一个比基准元素大的元素while (a[left] <= a[key] && right > left){left++;}// 如果左指针小于右指针,交换左右指针对应的元素if (left < right){swap(&a[left], &a[right]);}}// 将基准元素放到正确的位置上swap(&a[key], &a[left]);// 对基准元素左侧和右侧的子数组进行递归调用QuickSortHoare(a, begin, left - 1);QuickSortHoare(a, left + 1, end);
}

2、挖坑法

挖坑法是一种简洁的快速排序实现方式,它通过交替移动两个指针,将元素一个个填入坑位的方式来进行划分。与Hoare划分法相比,挖坑法在划分的过程中不需要频繁地交换元素,因此实现上会更为简单。

代码实现: 

void QuickSortLomuto(int* a, int begin, int end)
{if (begin >= end){return; // 递归结束条件:子数组只有一个元素或为空,不需要排序}int curi = begin; // 当前坑位的索引int temp = a[begin]; // 枢轴元素的值int left = begin; // 左指针int right = end; // 右指针while (left < right){// 从右侧开始,找到第一个比枢轴小的元素的索引while (a[right] >= temp && left < right){right--;}a[curi] = a[right]; // 将右指针指向的元素填入当前坑位curi = right; // 更新当前坑位的索引// 从左侧开始,找到第一个比枢轴大的元素的索引while (a[left] <= temp && left < right){left++;}a[curi] = a[left]; // 将左指针指向的元素填入当前坑位curi = left; // 更新当前坑位的索引}a[curi] = temp; // 将枢轴元素填入最后一个坑位,确保枢轴元素的位置被确定// 对枢轴左侧和右侧的子数组进行递归调用QuickSortLomuto(a, begin, left - 1);QuickSortLomuto(a, left + 1, end);
}

3、前后指针法 

 代码实现:

void QuickSortPB(int* a, int begin, int end)
{if(begin > end){return; // 递归结束条件:子数组为空,不需要排序}int prev = begin; // prev指针指向枢轴元素的位置int cur = prev + 1; // cur指针指向待比较元素的位置int key = begin; // 枢轴元素的位置// 遍历数组,将小于等于枢轴元素的元素放在prev指针的后面while(cur <= end){if(a[cur] <= a[key]){prev++;if(cur != prev){swap(&a[prev], &a[cur]); // 交换prev指针和cur指针指向的元素}}cur++;}swap(&a[begin], &a[prev]); // 将枢轴元素放到prev指针的位置// 对枢轴左侧和右侧的子数组进行递归调用QuickSortPB(a, begin, prev-1);QuickSortPB(a, prev+1, end);
}

二、快速排序优化 

"三数取中"的方法是从待排序数组中随机选择三个元素,然后取这三个元素的中间值作为枢轴元素。具体步骤如下:

  1. 选择开始位置 begin、结束位置 end 和中间位置 mid,计算 mid = (begin + end) / 2
  2. 比较 a[begin]a[mid] 和 a[end] 的大小,确定其中的中间值。
  3. 将选出的中间值与 a[begin] 进行交换,将中间值放在数组开始的位置,作为枢轴元素。
  4. 进行正常的快速排序算法。

通过使用"三数取中"的方法选择枢轴元素,可以尽量避免了最坏情况的发生。最坏的情况是枢轴元素选择的不好,导致每次划分只将数组分成一个很小的部分和一个很大的部分,使得快速排序的时间复杂度退化为O(n^2)。而"三数取中"的方法通过选择一个近似于中位数的元素作为枢轴,能够更平衡地划分数组,减少这种最坏情况的发生,从而提高快速排序的效率。

int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;  // 计算中间位置// begin midi end 三个数选中位数if (a[begin] < a[midi])  // 如果begin位置的值小于midi位置的值{if (a[midi] < a[end])  // 如果midi位置的值小于end位置的值return midi;  // 返回midi位置else if (a[begin] > a[end])  // 如果begin位置的值大于end位置的值return begin;  // 返回begin位置elsereturn end;  // 返回end位置}else {if (a[midi] > a[end])  // 如果midi位置的值大于end位置的值return midi;  // 返回midi位置else if (a[begin] < a[end])  // 如果begin位置的值小于end位置的值return begin;  // 返回begin位置elsereturn end;  // 返回end位置}
}int QuickSortHoare(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]);  // 将基准值放到中间位置return left;  // 返回中间位置的索引
}void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;  // 如果起始位置大于等于终止位置,则返回int keyi = QuickSortHoare(a, begin, end);  // 使用快速排序的Hoare分区函数找到基准值的索引QuickSort(a, begin, keyi - 1);  // 对基准值左边的数组进行快速排序QuickSort(a, keyi+1, end);  // 对基准值右边的数组进行快速排序
}

三、快速排序的非递归实现 

快速排序的非递归实现使用了栈来模拟递归的过程,这样可以避免使用系统栈导致的递归调用过深的问题。

非递归实现的思想如下:

  1. 创建一个栈,用于存储待处理子数组的边界。初始时,将整个数组的开始位置和结束位置入栈。

  2. 进行循环,直到栈为空。循环的目的是处理栈中的每个子数组。

  3. 弹出栈顶的子数组边界,得到开始位置和结束位置。

  4. 在子数组中选择枢轴元素,并使用 Hoare 划分方式将子数组划分成两个部分。将划分点的下标入栈,保证后续对其进行排序。

  5. 根据划分点的下标更新子数组边界,将左侧子数组的开始位置和结束位置入栈,再将右侧子数组的开始位置和结束位置入栈。注意保证先处理左侧子数组,再处理右侧子数组。

  6. 重复步骤 3 到步骤 5,直到栈为空,即所有子数组都被处理完毕。

通过上述步骤,可以将递归的快速排序算法转化为非递归的实现。该实现使用栈来保存待处理的子数组边界,以模拟递归过程。每次处理子数组时,选择一个枢轴元素进行划分,并将划分点的下标入栈。然后根据划分点将子数组分成两部分,分别将左侧和右侧的子数组边界入栈。这样可以确保先处理左侧子数组,再处理右侧子数组,达到快速排序的效果。

代码实现:

typedef int StackDataType;
typedef struct StackNode
{StackDataType data;struct StackNode* next;
}StackNode;typedef struct Stack
{StackNode* q;int size;
}Stack;void StackInit(Stack* s)
{StackNode* head=(StackNode*)malloc(sizeof(StackNode));head->next=NULL;s->q=head;s->size=0;
}void StackPush(Stack* s,StackDataType x)
{StackNode* newnode=(StackNode*)malloc(sizeof(StackNode));newnode->data=x;newnode->next=s->q->next;s->q->next=newnode;  s->size++;  
}int StackEmpty(Stack* s)
{if(s->q->next==NULL){return 1;}return 0;
}StackDataType StackTop(Stack* s)
{if(!StackEmpty(s)){return s->q->next->data;}else {return -1;}
}void StackPop(Stack* s)
{if(!StackEmpty(s)){StackNode* temp=s->q->next;s->q->next=s->q->next->next;free(temp);s->size--;}
}int get_keyi(int *a, int begin, int end)
{int left = begin;int right = end;int key = begin;while (left < right){// 从右侧开始,找到第一个小于 key 的元素while (a[right] >= a[key] && left < right){right--;}// 从左侧开始,找到第一个大于 key 的元素while (a[left] <= a[key] && left < right){left++;}// 交换找到的两个元素swap(&a[right], &a[left]);}// 将基准值放到中间位置swap(&a[key], &a[left]);return left; // 返回中间位置的索引
}void QuickSortNR(int* a, int begin, int end)
{Stack s;StackInit(&s);// 入栈起始和结束位置StackPush(&s, end);StackPush(&s, begin);int left, mid, right;// 当栈不为空时,循环执行while (!StackEmpty(&s)){left = StackTop(&s);StackPop(&s);right = StackTop(&s);StackPop(&s);// 如果左边界小于右边界,进行划分if (left < right){mid = get_keyi(a, left, right);}// 将左边未排序的部分入栈if (left < mid - 1){StackPush(&s, mid - 1);StackPush(&s, left);}// 将右边未排序的部分入栈if (right > mid + 1){StackPush(&s, right);StackPush(&s, mid + 1);}}
}

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

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

相关文章

【C++初阶】第二站:类与对象(上) -- 上部分

前言: C学习的第二站&#xff1a;类和对象(上)文章的上半部分,知识点:面向过程和面向对象初步认识、类的引入、类的定义、类的访问限定符及封装、类的作用域、类的实例化. 目录 面向过程和面向对象初步认识 类的引入 类的定义 类的访问限定符及封装 访问限定符 封装 类的…

【Docker】安装nacos以及实现负载均衡

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 前言 一.nacos单个部署 1.镜像拉取 …

【Linux】Linux系统编程——pwd命令

文章目录 1.命令概述2.命令格式3.常用选项4.相关描述5.参考示例 1.命令概述 pwd&#xff08;Print Working Directory&#xff09;命令用于显示用户当前工作目录的完整路径。这是一个常用的命令&#xff0c;帮助用户确定他们目前所在的目录位置。 2.命令格式 基本的 pwd 命令…

Excel表格的快速动态扩展与删除行

实例需求&#xff1a;工作表中的表格&#xff08;ListObject&#xff09;名称为Table1&#xff0c;表格列数不确定&#xff0c;需要实现如下功能&#xff1a; 当用户完成最后一行最后一列输入之后&#xff08;如果该单元格为空&#xff0c;则视为输入未完成&#xff09;&#…

Python 生成 文件目录网页 下载超链接和打开 笔记

1. 一键生成 文件目录网页 下载超链接 1.1 图&#xff1a; 1.2 代码&#xff1a; &#xff08;由Ai生成部分&#xff0c;再改成适合自己用&#xff09; index.py # -*- coding: utf-8 -*- import os # import syspath E:\BIT\public\software\\ # path path os.getcwd() …

Java基础知识整理,驼峰规则、流程控制、自增自减

写在开头 本文接着上一篇文章续写哈。Java基础知识整理&#xff0c;注释、关键字、运算符 在这一篇文章中我们总结了包括注释、关键字、运算符的Java基础知识点&#xff0c;今天继续来聊一聊命名规则&#xff08;驼峰&#xff09;、流程控制、自增自减。 一、命名规则 上一…

书生·浦语 LMDeploy 大模型量化部署原理

文章目录 大模型部署背景模型部署大模型的特点大模型部署的挑战大模型部署方案 LMDeploy简介 大模型部署背景 模型部署 将训练好的模型在特定软硬件环境中启动的过程&#xff0c;使模型能够接收输入并返回预测结果为了满足性能和效率的需求&#xff0c;常常需要对模型进行优化…

SpringBoot项目打包

1.在pom.xml中加入如下配置 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><descriptorRef…

CentOS stream 9配置网卡

CentOS stream9的网卡和centos 7的配置路径&#xff1a;/etc/sysconfig/network-scripts/ifcfg-ens32不一样。 CentOS stream 9的网卡路径&#xff1a; /etc/NetworkManager/system-connections/ens32.nmconnection 方法一&#xff1a; [connection] idens32 uuid426b60a4-4…

CTF CRYPTO 密码学-2

题目名称&#xff1a;crypto1 题目描述&#xff1a; 字符 ZZZZ X XXZ ZZ ZXZ Z ZXZ ZX ZZX XXX XZXX XXZ ZX ZXZZ ZZXZ XX ZX ZZ 分析 此字段是由Z和X组成的字符&#xff0c;联想到莫斯密码是由.和-组成的所以接下来可以尝试莫斯密码解题 解题过程&#xff1a; Step1&#xf…

双亲委派模型

其实分两步&#xff1a;检查&#xff08;自底而上&#xff09;—>加载&#xff08;自顶向下&#xff09; 看个例子&#xff1a;Class.forName(“com.mysql.cj.jdbc.Driver”); 但是&#xff01;&#xff01;&#xff01;在JDBC4.0之后就不需要这行代码了&#xff0c;使…

【解决问题】java项目启动缓慢原因排查

背景&#xff1a; 最近启动项目&#xff0c;特别慢&#xff0c;尤其在中间部分会卡住几分钟&#xff0c;并且不打印任何日志 问题排查&#xff1a; 针对上面的问题&#xff0c;需要使用jdk自带的打印堆栈信息命令排查&#xff0c;这里我们采用命令跟idea工具两种方式 方式1-…