数据结构——快速排序
快速排序
在交换排序中,快速排序是效率极高的一种,它基于“分治法”思想,通过选择一个“基准元素”将数组分成两部分,再递归处理这两部分,最终实现整个数组的有序排列。这种排序方式的核心是“一趟划分”——每完成一次划分,基准元素会被放到最终的正确位置,左边的元素都比基准小,右边的元素都比基准大,后续只需递归处理左右子数组即可。
1. 快速排序的核心思想与执行流程
快速排序的核心逻辑可概括为“选基准、做划分、递归根”,具体步骤如下:
- 选基准:从待排序数组中选择一个元素作为“基准”(常见选择是数组第一个元素、最后一个元素或中间元素);
- 做划分:通过左右指针移动,将数组分成两部分——左部分所有元素小于基准,右部分所有元素大于基准,基准元素放在两部分中间的最终位置;
- 递归根:递归地对左部分和右部分重复“选基准、做划分”的过程,直到子数组的长度为1(此时子数组已有序)。
我们以数组arr = {49, 38, 65, 97, 76, 13, 27, 49}为例,详细展示快速排序的执行流程(选择数组第一个元素49作为基准):
-
初始数组:
[49, 38, 65, 97, 76, 13, 27, 49](待排序区间:0~7) -
第一趟划分(确定基准49的位置):
- 初始化左指针
i=0(指向基准),右指针j=7,基准值pivot=49; - 右指针
j向左移动,找第一个小于基准的元素:j=6时元素27<49,停止移动,将arr[j](27)放到arr[i](0位置),数组变为[27, 38, 65, 97, 76, 13, 27, 49],i++(i=1); - 左指针
i向右移动,找第一个大于基准的元素:i=2时元素65>49,停止移动,将arr[i](65)放到arr[j](6位置),数组变为[27, 38, 65, 97, 76, 13, 65, 49],j--(j=5); - 重复步骤2-3:右指针
j=5时元素13<49,放到arr[i](2位置),数组变为[27, 38, 13, 97, 76, 13, 65, 49],i++(i=3);左指针i=3时元素97>49,放到arr[j](5位置),数组变为[27, 38, 13, 97, 76, 97, 65, 49],j--(j=4); - 继续移动:右指针
j=4时元素76>49,j--(j=3),此时i==j(i=3,j=3),将基准值49放到arr[i](3位置),划分结束。
第一趟划分后,数组变为[27, 38, 13, 49, 76, 97, 65, 49],基准49在位置3,左子数组(0~2):[27, 38, 13],右子数组(4~7):[76, 97, 65, 49]。
- 初始化左指针
-
递归处理左子数组(0~2):
选基准27,划分后左子数组(00):`[27]`(有序),右子数组(22):[13](经划分后有序),最终左子数组变为[13, 27, 38]。 -
递归处理右子数组(4~7):
选基准76,划分后左子数组(4~6):[65, 49](经递归划分后变为[49, 65]),右子数组(7~7):[97](有序),最终右子数组变为[49, 65, 76, 97]。 -
最终有序数组:合并左右子数组和基准,得到
[13, 27, 38, 49, 49, 65, 76, 97]。
2. 快速排序的代码实现
快速排序的代码核心是“一趟划分函数”和“递归排序函数”,以下是基于左指针基准的C语言实现,代码精简且注释详细:
// 一趟划分:返回基准最终位置,左小右大
int Partition(int arr[], int low, int high) {int pivot = arr[low]; // 选左端点为基准while (low < high) {// 右指针找小于基准的元素while (low < high && arr[high] >= pivot) high--;arr[low] = arr[high]; // 放到左指针位置// 左指针找大于基准的元素while (low < high && arr[low] <= pivot) low++;arr[high] = arr[low]; // 放到右指针位置}arr[low] = pivot; // 基准归位return low; // 返回基准位置
}// 快速排序:递归处理左右子数组
void QuickSort(int arr[], int low, int high) {if (low < high) { // 子数组长度>1才处理int pivotPos = Partition(arr, low, high); // 划分QuickSort(arr, low, pivotPos-1); // 左子数组QuickSort(arr, pivotPos+1, high); // 右子数组}
}
代码说明:
Partition函数:实现一趟划分,通过左右指针交替移动,将小于基准的元素移到左边,大于基准的移到右边,最后返回基准位置;QuickSort函数:递归调用,若子数组区间low < high(长度大于1),则先划分,再递归处理左(low~pivotPos-1)、右(pivotPos+1~high)子数组。
3. 快速排序的性能与特性
-
时间复杂度:
- 最好情况(每次划分均匀,左右子数组长度接近):每趟划分处理n个元素,递归深度为
logn,总时间复杂度为O(nlogn); - 最坏情况(数组有序,选两端为基准,划分后一边为空):递归深度为
n,总时间复杂度为O(n²); - 平均情况:通过随机选择基准可避免最坏情况,平均时间复杂度为
O(nlogn),是所有O(nlogn)排序算法中实际运行速度较快的一种。
- 最好情况(每次划分均匀,左右子数组长度接近):每趟划分处理n个元素,递归深度为
-
空间复杂度:空间开销来自递归栈,最好/平均递归深度为
logn,空间复杂度O(logn);最坏递归深度为n,空间复杂度O(n),属于“原地排序”(无额外数组开销)。 -
稳定性:划分过程中,相同元素可能被交换到基准两侧(如示例中的两个49),相对顺序改变,因此快速排序是不稳定的排序算法。
4. 适用场景
快速排序适合以下场景:
- 数据量较大的无序数组(如n>1000):
O(nlogn)的平均时间复杂度能保证高效处理; - 对排序速度要求高,且不要求稳定性的场景(如普通数据统计、非业务关键数据排序);
- 内存资源有限的场景:原地排序特性可减少内存开销。
综上,快速排序通过分治法和一趟划分,实现了高效的排序,核心是“基准归位+递归处理”。虽然存在最坏情况,但通过合理选择基准(如随机基准、三数取中)可大幅降低最坏情况概率,使其成为实际应用中最常用的排序算法之一。理解一趟划分的指针移动逻辑和递归流程,是掌握快速排序的关键。
