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

数据结构八大排序:快速排序-挖坑法(递归与非递归)及其优化

引言

快速排序到底有多块?通过实验对各种排序算法做了对比(单位:毫秒),对比结果如下表所示。

由此可见面对大量数据快速排序的速度是冒泡排序速度的数千倍,那么今天就来学习快速排序的过程思路和代码实现。

一、快速排序算法概述

快速排序是由Tony Hoare在1960年提出的一种高效的排序算法,采用分治策略。其核心思想是:选择一个基准元素,通过一趟排序将待排记录分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据小,然后再按此方法对这两部分数据分别进行快速排序。

基本特性:

 平均时间复杂度:O(nlogn)             最坏时间复杂度:O(n²)             最好情况:O(nlogn) 

 空间复杂度:O(logn)                      稳定性:不稳定排序

二、挖坑法快速排序原理

2.1 挖坑法基本思想

挖坑法是快速排序的一种实现方式,其核心思路是:

  1. 选取一个基准值(pivot),默认以最左边的值为基准值

  2. 将基准值的位置作为第一个"坑"

  3. 从右向左找比基准值小的放在坑位,再从左向右找比基准值大的放在坑位,每找到一次满足条件的数切换一下左右,一直循环直到左指针遇到右指针

  4. 最终将基准值放入最后一个坑位

  5. 再以基准值放入的坑位为分界线,分成若干个数据段再对每个数据段进行上面的排序

2.2 排序过程图解

以数组 [5, 3, 8, 6, 2, 7, 1, 4] 为例:

初始: [5, 3, 8, 6, 2, 7, 1, 4]↑                    ↑left                rightpivot=5, 坑在left位置步骤1: 从右向左找小于5的数,找到4
[_, 3, 8, 6, 2, 7, 1, 4] → [4, 3, 8, 6, 2, 7, 1, _]坑移到right位置步骤2: 从左向右找大于5的数,找到8
[4, 3, _, 6, 2, 7, 1, 8] → 坑移到原8的位置继续交替扫描,最终将5放入正确位置...

三、递归实现挖坑法快速排序

#include <stdio.h>
void quickSortRecursive(int arr[], int left, int right) {if (left >= right) return;int i = left, j = right, pivot = arr[left];while (i < j) {while (i < j && arr[j] >= pivot) j--;if (i < j) arr[i++] = arr[j];while (i < j && arr[i] <= pivot) i++;if (i < j) arr[j--] = arr[i];}arr[i] = pivot;quickSortRecursive(arr, left, i - 1);quickSortRecursive(arr, i + 1, right);
}
void printArray(int arr[], int n) {for (int i = 0; i < n; i++) printf("%d ", arr[i]);printf("\n");
}
int main() {int arr[] = {5, 3, 8, 6, 2, 7, 1, 4};int n = sizeof(arr) / sizeof(arr[0]);printf("原数组: ");printArray(arr, n);quickSortRecursive(arr, 0, n - 1);printf("排序后: ");printArray(arr, n);return 0;
}

四、非递归实现挖坑法快速排序

#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct Stack {int data[MAX_SIZE];int top;
} Stack;
void push(Stack *s, int x) {if (s->top < MAX_SIZE - 1) s->data[++s->top] = x;
}
int pop(Stack *s) {if (s->top >= 0) return s->data[s->top--];return -1;
}
int isEmpty(Stack *s) {return s->top == -1;
}
void quickSortNonRecursive(int arr[], int left, int right) {Stack s; s.top = -1;push(&s, left); push(&s, right);while (!isEmpty(&s)) {int r = pop(&s); int l = pop(&s);if (l >= r) continue;int i = l, j = r, pivot = arr[l];while (i < j) {while (i < j && arr[j] >= pivot) j--;if (i < j) arr[i++] = arr[j];while (i < j && arr[i] <= pivot) i++;if (i < j) arr[j--] = arr[i];}arr[i] = pivot;push(&s, l); push(&s, i - 1);push(&s, i + 1); push(&s, r);}
}
int main() {int arr[] = {5, 3, 8, 6, 2, 7, 1, 4};int n = sizeof(arr) / sizeof(arr[0]);printf("原数组: ");for (int i = 0; i < n; i++) printf("%d ", arr[i]);printf("\n");quickSortNonRecursive(arr, 0, n - 1);printf("非递归排序后: ");for (int i = 0; i < n; i++) printf("%d ", arr[i]);printf("\n");return 0;
}

五、快速排序的优化策略

5.1 三数取中法选择基准

int getMidIndex(int arr[], int left, int right) {int mid = left + (right - left) / 2;if (arr[left] > arr[mid]) {if (arr[mid] > arr[right]) return mid;else if (arr[left] > arr[right]) return right;else return left;} else {if (arr[left] > arr[right]) return left;else if (arr[mid] > arr[right]) return right;else return mid;}
}

5.2 小区间量使用直接插入排序

void insertSort(int arr[], int left, int right) {for (int i = left + 1; i <= right; i++) {int key = arr[i], j = i - 1;while (j >= left && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;}
}
void optimizedQuickSort(int arr[], int left, int right) {if (right - left <= 10) {insertSort(arr, left, right);return;}int mid = getMidIndex(arr, left, right);int temp = arr[left]; arr[left] = arr[mid]; arr[mid] = temp;int i = left, j = right, pivot = arr[left];while (i < j) {while (i < j && arr[j] >= pivot) j--;if (i < j) arr[i++] = arr[j];while (i < j && arr[i] <= pivot) i++;if (i < j) arr[j--] = arr[i];}arr[i] = pivot;optimizedQuickSort(arr, left, i - 1);optimizedQuickSort(arr, i + 1, right);
}

六、复杂度分析与比较

6.1 时间复杂度分析

        最好情况:O(nlogn) - 每次划分都很均匀

        平均情况:O(nlogn)

        最坏情况:O(n²) - 数组已排序或逆序

6.2 空间复杂度分析

        递归版本:O(logn) - 递归调用栈深度

        非递归版本:O(logn) - 手动维护的栈空间

6.3 稳定性分析

快速排序是不稳定的排序算法,因为相同元素的相对位置可能在分区过程中改变。

七、递归与非递归版本比较

特性递归版本非递归版本
代码复杂度简单易懂相对复杂
栈空间系统栈,可能溢出手动控制,更安全
性能函数调用开销无函数调用开销
调试难度较难调试相对容易调试

八、使用场景与选择建议

8.1 选择递归版本的情况:

        数据规模不是特别大                 代码简洁性更重要                        栈深度不会太大

8.2 选择非递归版本的情况:

        数据规模很大,担心栈溢出                对性能有极致要求                需要更好的调试体验

8.3 选择优化版本的情况:

        数据可能部分有序                对性能稳定性要求高                处理大数据集

九、注意事项与最佳实践

  1. 边界处理:始终检查left < right

  2. 基准选择:避免最坏情况,使用三数取中

  3. 递归深度:大数据集考虑非递归版本

  4. 内存使用:注意栈空间限制

  5. 稳定性:需要稳定排序时选择其他算法

十、常见面试题

10.1 基础概念题

  1. 快速排序的时间复杂度是多少?最坏情况如何避免?

  2. 为什么快速排序是不稳定的?

  3. 快速排序和归并排序的主要区别是什么?

10.2 编码实现题

// 题目1:单链表快速排序
struct ListNode {int val;struct ListNode *next;
};
struct ListNode* quickSortList(struct ListNode* head) {if (!head || !head->next) return head;int pivot = head->val;struct ListNode *less = NULL, *equal = NULL, *greater = NULL;struct ListNode **l = &less, **e = &equal, **g = &greater;while (head) {if (head->val < pivot) { *l = head; l = &(*l)->next; }else if (head->val == pivot) { *e = head; e = &(*e)->next; }else { *g = head; g = &(*g)->next; }head = head->next;}*l = *e = *g = NULL;less = quickSortList(less);greater = quickSortList(greater);struct ListNode *result = less;while (less && less->next) less = less->next;if (less) less->next = equal;else result = equal;while (equal && equal->next) equal = equal->next;if (equal) equal->next = greater;else result = greater;return result;
}

10.3 算法分析题

  1. 给定10^6个整数,如何选择快速排序的优化策略?

  2. 如何在O(n)时间内找到数组的第k大元素?

  3. 快速排序在什么情况下会退化为O(n²)?如何检测这种情况?

总结

快速排序是实践中最高效的排序算法之一,掌握其挖坑法实现及各种优化技巧对于程序员至关重要。递归版本代码简洁,非递归版本更安全可靠,优化版本能处理各种边界情况。在实际应用中,应根据具体场景选择合适的实现方式,并注意算法的时间、空间复杂度以及稳定性要求。

http://www.dtcms.com/a/512168.html

相关文章:

  • Docker 中卷、容器、镜像的区别
  • 学习React-21-受控组件非受控组件
  • 银行测试学习计划
  • 电商自建站中企动力网站建设公司
  • 怎么搜 织梦的网站唐山海港经济开发区人才网
  • Qt打包工具Enigma Virtual Box
  • 【同步/异步 日志系统】--- 介绍
  • 【软考备考】 数据与文件的加解密种类详解和使用场景
  • GitLab 版本控制与管理指南
  • Python动态方法调用全解:从字符串到执行的艺术
  • Blender入门学习03
  • 网站建设龙兵科技嘉兴网站建设网站建设
  • html代码下载网站怎么优化关键词
  • Kafka面试精讲 Day 27:监控告警与故障排查
  • C++ ABI:编译报错之:gcc 4.8.1 切 gcc 6.1.0
  • OLED-on-silicon(OLEDoS)技术正成为VR/MR设备显示技术的未来大趋势
  • QML学习笔记(四十四)QML与C++交互:对QML对象设置objectName
  • 网站制作全包多少钱演出票务网站建设
  • 用 Go 手搓一个 NTP 服务:从“时间混乱“到“精准同步“的奇幻之旅
  • 如何设计一个高并发系统?
  • 仓颉语言核心技术全解析与实战教程
  • 【多维聚类算法】RQ-Kmeans 利用残差信息 捕捉细节特征
  • 【代码随想录算法训练营——Day44】动态规划——1143.最长公共子序列、1035.不相交的线、53.最大子序和、392.判断子序列
  • 北住房和城乡建设厅网站亦庄建设局网站
  • 做生鲜食品最好的网站深圳网站建设犀牛云
  • Spring—容器
  • 汉南公司网站建设山东定制版网站建设公司
  • .NET WinForms + WPF 综合学习路线:从传统到现代的.NET桌面开发
  • 怀柔做网站设计师网上接单被骗
  • Go语言实战:入门篇-4:与数据库、redis、消息队列、API