当前位置: 首页 > backend >正文

快速排序(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.非递归法

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

//快速排序(非递归)
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.xdnf.cn/news/10899.html

相关文章:

  • 第一章-计算机系统概述深化
  • AI数字人技术革新进行时:井云数字人如何重塑人机交互未来?
  • 瑞幸咖啡香港自营门店增至 12 间 未来或拓展至中环等核心区
  • 问题七、isaacsim中添加IMU传感器
  • one-hot编码VS对象嵌入表示
  • docker创建postgreSql带多个init的sql
  • 工厂模式与多态结合
  • 通信算法之281:大疆DJI无人机ID-DJI DroneID开源工程-相关问题-协议信息问题
  • 【高等数学】(2)函数
  • MongoDB数据库学习
  • 【JS服务器】JETBRAINS IDEs JS服务器使用什么编译JNI
  • Docker或Docker-Compose时间时区配置
  • 【亲测有效 | Cursor Pro每月500次快速请求扩5倍】(Windows版)Cursor中集成interactive-feedback-mcp
  • 工业智能网关保障冷冻仓储设备无人值守安全运行
  • 当 “欧洲版 Cursor” 遇上安全危机
  • 7.RV1126-OPENCV cvtColor 和 putText
  • 软件架构文档最少编写规范
  • 【软考】计算机系统构成及硬件基础知识
  • 如何在PowerBI中使用Analyze in Excel
  • 1130 - Host ‘xxx.x.xx.xxx‘is not allowed to connect to this MySQL server
  • 网络安全-等级保护(等保)3-0 等级保护测评要求现行技术标准
  • Linux系统-基本指令(5)
  • 大话软工笔记—分离之组织和物品
  • 基于SDN环境下的DDoS异常攻击的检测与缓解
  • C++ Learning string类模拟实现
  • ADI硬件笔试面试题型解析下
  • 晶台光耦在手机PD快充上的应用
  • 古典密码学介绍
  • 物联网数据归档方案选择分析
  • 【Pandas】pandas DataFrame rename_axis