快速排序(Quick Sort)算法详解(递归与非递归)

引言

在计算机科学中,排序算法是最基础且重要的算法之一。快速排序(Quick Sort)作为一种高效的排序算法,在实际应用中被广泛使用。平均时间复杂度为 (O(n log n)),最坏情况下为 (O(n^2))。本文将详细介绍快速排序算法的原理,结合具体代码进行分析,给出两种递归方法与一种非递归方法,并给出两种优化方案。

快速排序原理

快速排序的基本思想是通过选择一个基准元素(key),将数组分为两部分,使得左边部分的所有元素都小于等于基准元素,右边部分的所有元素都大于等于基准元素。然后递归地对左右两部分进行排序,最终得到一个有序的数组。

具体步骤如下:

  1. 选择基准元素:从数组中选择一个元素作为基准元素。
  2. 分区操作:将数组中的元素重新排列,使得所有小于等于基准元素的元素都在基准元素的左边,所有大于等于基准元素的元素都在基准元素的右边。这个过程称为分区(partition)。
  3. 递归排序:对左右两部分分别递归地应用快速排序算法。

代码实现

1.hoare法(递归)

/交换
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//快速排序
void QuickSort(int* a, int left, int right)
{if(left >= right)return;int key = left;int begin = left, 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;QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}

 2.双指针法(递归)

//快排递归:双指针
int PartSort2(int* a, int left, int right)
{//三数取中(优化)int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;int prev = left;int cur = prev + 1;while(cur <= right){if(a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[key]);return prev;
}
//快速排序(递归)
void QuickSort(int* a, int left, int right)
{if(left >= right)return;//小区间优化if((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}else{// int key = PartSort1(a, left, right);int key = PartSort2(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}

代码解释:
  1. Swap 函数:用于交换两个整数的值。
  2. QuickSort 函数
    • 递归终止条件:当 left >= right 时,说明数组已经有序,直接返回。
    • 分区操作:使用双指针法将数组分为两部分,左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素。
    • 递归排序:对左右两部分分别递归地调用 QuickSort 函数。

3.非递归法

递归方法来实现排序终归有栈溢出的风险,所以这里提供一种非递归方式实现快速排序。

先给定栈的实现

//Stack.h
#pragma once#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>typedef int STDataType;typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;//初始化与销毁
void STInit(ST* pst);
void STDestory(ST* pst);//入栈 出栈
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);//取栈顶数据
STDataType STTop(ST* pst);//判空
bool STEmpty(ST* pst);//获取数据的个数
int STSize(ST* pst);
//Stack.c
#include "Stack.h"//初始化与销毁
void STInit(ST* pst)
{assert(pst);pst->a = NULL;pst->top = 0;pst->capacity = 0;
}
void STDestory(ST* pst)
{assert(pst);free(pst->a);pst->a = NULL;pst->top = pst->capacity = 0;
}//入栈 出栈
void STPush(ST* pst, STDataType x)
{assert(pst);//扩容if(pst->top == pst->capacity){int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity* sizeof(STDataType));if(tmp == NULL){perror("realloc fail");exit(1);}pst->a = tmp;pst->capacity = newcapacity;}    //入栈pst->a[pst->top] = x;pst->top++;}
void STPop(ST* pst)
{assert(pst);assert(pst->top > 0);pst->top--;
}//取栈顶数据
STDataType STTop(ST* pst)
{assert(pst);assert(pst->top > 0);return pst->a[pst->top - 1];
}//判空
bool STEmpty(ST* pst)
{assert(pst);return pst->top == 0;
}//获取数据的个数
int STSize(ST* pst)
{assert(pst);return pst->top;
}

 运用栈来实现非递归快排:

//快速排序(非递归)
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 key = PartSort2(a, begin, end);if(end > key + 1){STPush(&st, end);STPush(&st, key + 1);}if(begin < key - 1){STPush(&st, key - 1);STPush(&st, begin);}}STDestory(&st);
}

这里使用栈来存储指针begin与end ,栈在堆中存储,能通过malloc不断申请内存空间,有效规避了栈溢出的风险。

测试代码

下面对快速排序做一个简单的测试:

void TestOP()
{srand((unsigned int)time(NULL));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);for(int i = 0; i < N; i++){a1[i] = rand();}int begin1 = clock();// 此处可调用 QuickSort 进行测试QuickSort(a1, 0, N - 1);int end1 = clock();printf("QuickSort:%d\n", end1 - begin1);
}int main()
{TestOP();return 0;
}

在这个测试文件中,我们生成了一个包含 100000 个随机整数的数组,并调用 QuickSort 函数对其进行排序,最后输出排序所需的时间。

复杂度分析

  • 时间复杂度:平均情况下为 (O(n log n)),最坏情况下为 (O(n^2))。
  • 空间复杂度:递归调用栈的深度为 (O(log n)),因此空间复杂度为 (O(log n))。

优化方案 

1.三数取中

若数组排序前就有序,时间复杂度为O(n^2),那么此时三数取中就是消除这种接近有序带来的大量重复计算的优化方法

代码实现

//三数取中
int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;// left midi rightif (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] < a[right]){return right;}else{return left;}}else // a[left] > a[mid]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]){return left;}else{return right;}}
}

紧接着再把返回的mid值与left处值交换即可

//三数取中(优化)int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);

2.小区间优化

我们通过前面所学内容可以得知快排的递归是类似与二叉树递归的一种算法,那么高度越高所消耗的空间越大,每个递归函数的调用会建立一个函数栈帧,为了避免出现栈溢出的情况,我们可以采取小区间优化来规避风险并高效实现排序。

代码实现

(可任选一种时间复杂度为O(n^2)的排序,这里选择更为高效的插入排序。)

//插入排序 最好O(N) 最坏O(N ^ 2)
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--;}else{break;}}a[end + 1] = tmp;}
}
//小区间优化if((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}

3.其他

除了这些优化方案,可能有人会觉得快速排序难以理解,这里还有一种较为通俗的挖坑法(并没有改变排序效率,只是更为通俗易懂),可以自行查阅资料。

总结

快速排序是一种高效的排序算法,通过分治法和分区操作,将数组不断地分为两部分进行排序。在实际应用中,通过三数取中和小区间优化,可以提高算法的性能。希望本文对你理解快速排序算法有所帮助。

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

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

相关文章

修改 vscode 左侧导航栏的文字大小 (更新版)

新增, 个人常用 按 Ctrl Shift P 打开命令面板 输入并选择 : Developer: Toggle Developer Tools 打开开发者工具。 1. 起因&#xff0c; 目的: 问题&#xff1a; vscode 左侧的文字太小了&#xff01;&#xff01;&#xff01;我最火的一篇文章&#xff0c;写的就是这个…

Kerberos面试内容整理-Kerberos 的配置与排障

正确配置 Kerberos 对其正常工作至关重要。在Linux/Unix环境下,Kerberos配置通常通过编辑配置文件(例如 /etc/krb5.conf)完成。其中指定了Realm名称、KDC和管理员服务器地址、默认域到Realm的映射等参数。管理员需要在KDC端初始化数据库并创建主体(可以使用 kadmin 等工具添…

Windows + CPU也能跑时序预测:TSLib框架快速上手与踩坑避雷

在时序预测领域,选择一个成熟的框架往往能让我们事半功倍。最近接手了一个紧急的时序预测项目,经过一番调研后,我选择了TSLib(Time-Series-Library)这个优秀的开源框架来快速搭建整个预测流程。 由于开发环境限制在Windows平台且没有GPU支持,整个部署过程还是遇到了一些…

从 0 到 1:用 Trae 插件 Builder 模式开发端午包粽子小游戏

​ 前言 Trae插件获取&#xff1a;https://www.trae.com.cn/plugin 在编程的世界里&#xff0c;效率就是生命。我们开发者常常为了一个项目的搭建&#xff0c;重复着创建文件夹、初始化项目配置、编写样板代码等一系列繁琐的操作&#xff0c;耗费了大量的时间和精力。而如今…

React-native之Flexbox

本文总结: 我们学到了 React Native 的 Flexbox 布局&#xff0c;它让写样式变得更方便啦&#xff01;&#x1f60a; Flexbox 就像一个有弹性的盒子&#xff0c;有主轴和交叉轴&#xff08;行或列&#xff09;。 在 RN 里写样式要用 StyleSheet.create 对象&#xff0c;属性名…

Leetcode 1336. 每次访问的交易次数

1.题目基本信息 1.1.题目描述 表: Visits ---------------------- | Column Name | Type | ---------------------- | user_id | int | | visit_date | date | ---------------------- (user_id, visit_date) 是该表的主键(具有唯一值的列的组合) 该表的每行表示 use…

腾讯云国际版和国内版账户通用吗?一样吗?为什么?

在当今全球化的数字化时代&#xff0c;云计算服务成为众多企业和个人拓展业务、存储数据的重要选择。腾讯云作为国内领先的云服务提供商&#xff0c;其国际版和国内版备受关注。那么&#xff0c;腾讯云国际版和国内版账户是否通用&#xff1f;它们究竟一样吗&#xff1f;背后又…

解锁Java多级缓存:性能飞升的秘密武器

一、引言 文末有彩蛋 在当今高并发、低延迟的应用场景中&#xff0c;传统的单级缓存策略往往难以满足性能需求。随着系统规模扩大&#xff0c;数据访问的瓶颈逐渐显现&#xff0c;如何高效管理缓存成为开发者面临的重大挑战。多级缓存架构应运而生&#xff0c;通过分层缓存设…

Android Kotlin 算法详解:链表相关

前言 &#x1f60a; 在 Android 开发中&#xff0c;算法与数据结构是基本功之一&#xff0c;而链表&#xff08;Linked List&#xff09;作为常见的数据结构&#xff0c;经常出现在各类面试题与实际业务场景中。本文将以 Android Kotlin 为语言&#xff0c;结合 LeetCode 上的…

Blinko智能笔记系统实现跨平台同步与隐私保护的完整技术方案解析

文章目录 前言1. Docker Compose一键安装2. 简单使用演示3. 安装cpolar内网穿透4. 配置公网地址5. 配置固定公网地址 推荐 ​ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站 前言 是否…

【小红书】API接口,获取笔记列表

小红书笔记列表API接口详解 - 深圳小于科技助力高效数据对接 深圳小于科技&#xff08;官网&#xff1a;https://www.szlessthan.com&#xff09;提供的小红书笔记列表API接口&#xff0c;帮助开发者快速获取小红书平台笔记数据&#xff0c;实现高效内容管理与分析。 接口概述…

Qwen2.5-VL 损失函数

Qwen2.5-VL 损失函数 flyfish 文章名称链接深入理解交叉熵损失 CrossEntropyLoss - 概率基础链接深入理解交叉熵损失 CrossEntropyLoss - 对数链接深入理解交叉熵损失 CrossEntropyLoss - 概率分布链接深入理解交叉熵损失 CrossEntropyLoss - 信息论&#xff08;交叉熵&#…

LeetCode 1524. 和为奇数的子数组数目

好的&#xff01;让我们详细解释 LeetCode 1524. 和为奇数的子数组数目 这道题的思路和解法。 题目&#xff1a; https://leetcode.cn/problems/number-of-sub-arrays-with-odd-sum/description/ 题目分析 问题描述&#xff1a; 给定一个整数数组 arr&#xff0c;返回其中和…

kafka学习笔记(三、消费者Consumer使用教程——消费性能多线程提升思考)

1.简介 KafkaConsumer是非线程安全的&#xff0c;它定义了一个acquire()方法来检测当前是否只有一个线程在操作&#xff0c;如不是则会抛出ConcurrentModifcationException异常。 acquire()可以看做是一个轻量级锁&#xff0c;它仅通过线程操作计数标记的方式来检测线程是否发…

百度蜘蛛池的作用是什么?技术@baidutopseo

百度蜘蛛池的作用 百度蜘蛛池是一种用于集中管理和调度百度搜索引擎蜘蛛&#xff08;爬虫&#xff09;的技术工具。其主要目的是优化网站内容的收录效率&#xff0c;帮助搜索引擎更高效地发现和抓取网页内容。技术baidutopseo 提升收录效率 通过集中大量百度蜘蛛的访问请求&…

黑马Java面试笔记之 消息中间件篇(Kafka)

一. Kafka保证消息不丢失 Kafka如何保证消息不丢失 使用Kafka在消息的收发过程中都会出现消息丢失&#xff0c;Kafka分别给出了解决方案 生产者发送消息到Brocker丢失消息在Brocker中存储丢失消费者从Brocker接收消息丢失 1.1 生产者发送消息到Brocker丢失 设置异步发送 消息…

dis css port brief 命令详细解释

华为交换机命令 display css port brief 详细解释 display css port brief 是华为交换机中用于 快速查看堆叠&#xff08;CSS&#xff0c;Cluster Switch System&#xff09;端口状态及关键参数 的命令&#xff0c;适用于日常运维、堆叠链路健康检查及故障定位。以下是该命令的…

Redis 缓存问题及其解决方案

1. 缓存雪崩 概念&#xff1a;缓存雪崩是指在缓存层出现大范围缓存失效或缓存服务器宕机的情况下&#xff0c;大量请求直接打到数据库&#xff0c;导致数据库压力骤增&#xff0c;甚至可能引发数据库宕机。 影响&#xff1a;缓存雪崩会导致系统性能急剧下降&#xff0c;甚至导…

使用Python进行函数作画

前言 因为之前通过deepseek绘制一下卡通的人物根本就不像&#xff0c;又想起来之前又大佬通过函数绘制了一些图像&#xff0c;想着能不能用Python来实现&#xff0c;结果发现可以&#xff0c;不过一些细节还是需要自己调整&#xff0c;deepseek整体的框架是没有问题&#xff0…

关于list集合排序的常见方法

目录 1、list.sort() 2、Collections.sort() 3、Stream.sorted() 4、进阶排序技巧 4.1 空值安全处理 4.2 多字段组合排序 4.3. 逆序 5、性能优化建议 5.1 并行流加速 5.2 原地排序 6、最佳实践 7、注意事项 前言 Java中对于集合的排序操作&#xff0c;分别为list.s…