快速排序
QuickSort 是一种基于分而治之算法的排序算法,它选择一个元素作为主元,并通过将主元放置在已排序数组中的正确位置,围绕所选主元对给定数组进行分区。
快速排序是如何工作的?
QuickSort 中的关键过程是partition()。分区的目标是将主元素(可以选择任何元素作为主元)放置在已排序数组中的正确位置,并将所有较小的元素放在主元的左侧,将所有较大的元素放在主元的右侧。
将枢轴放置在正确的位置后,在枢轴的每一侧递归地完成分区,最终对数组进行排序。
枢轴的选择:
选择枢轴有许多不同的选择。
- 始终选择第一个元素作为枢轴.
- 始终选择最后一个元素作为基准
- 选择一个随机元素作为基准
- 选择中间作为枢轴
分区算法:
逻辑很简单,我们从最左边的元素开始,并跟踪较小(或等于)元素的索引 i。遍历时,如果找到较小的元素,则将当前元素与 arr[i] 交换。否则,我们忽略当前元素。
让我们借助以下示例来了解分区和快速排序算法的工作原理:
考虑:arr[] = {10, 80, 30, 90, 40}。
- 将10与枢轴比较,小于枢轴则依次排列。
- 将 80 与枢轴进行比较。它大于枢轴。
- 将 30 与枢轴进行比较。它小于枢轴,因此相应地安排它到相应的位置。
将 90 与枢轴进行比较。它大于枢轴。
将枢轴布置在正确的位置。
快速排序的说明:
由于分区过程是递归完成的,因此它不断地将主元放在排序数组中的实际位置。反复将枢轴放入其实际位置可以使数组排序。
按照下图了解分区算法的递归实现如何帮助对数组进行排序。
- 主阵列上的初始分区:
- 子数组的划分:
快速排序的代码实现:
javaScript实现:
js
// 将数组进行分区并返回分区索引的函数
function partition(arr, low, high) {
// 选择中心点
let pivot = arr[high];
// 较小元素的索引,表示目前找到的枢轴的正确位置
let i = low - 1;
for (let j = low; j <= high - 1; j++) {
// 如果当前元素小于枢轴
if (arr[j] < pivot) {
// 增加较小元素的索引
i++;
[arr[i], arr[j]] = [arr[j], arr[i]]; // 交换元素
}
}
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]]; // Swap pivot to its correct position
return i + 1; // 返回分区索引
}
// 实现快速排序的主要函数
function quickSort(arr, low, high) {
if (low < high) {
// pi是分区索引,arr[pi]现在位于正确的位置
let pi = partition(arr, low, high);
// 在分区之前和之后分别对元素进行排序
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
let arr = [10, 7, 8, 9, 1, 5];
let N = arr.length;
// 函数调用
quickSort(arr, 0, N - 1);
console.log("Sorted array:");
console.log(arr.join(" "));
PHP实现
php
<?php
// 此函数将最后一个元素作为枢轴
// 将枢轴放置在正确的位置上
// 在已排序的数组中,将所有较小的元素放在枢轴左侧,
// 将所有较大的元素放在枢轴右侧
function partition(&$arr,$low,$high)
{
// 选择枢轴元素
$pivot= $arr[$high];
// 较小元素的索引,并指示枢轴的正确位置
$i=($low-1);
for($j=$low;$j<=$high-1;$j++)
{
if($arr[$j]<$pivot)
{
// 增加较小元素的索引
$i++;
list($arr[$i],$arr[$j])=array($arr[$j],$arr[$i]);
}
}
// 将枢轴元素放置在正确的位置
list($arr[$i+1],$arr[$high])=array($arr[$high],$arr[$i+1]);
return ($i+1);
}
// 实现快速排序的主函数
// arr[]:要排序的数组
// low:起始索引
// high:结束索引
function quickSort(&$arr,$low,$high)
{
if($low<$high)
{
// pi是分区
$pi= partition($arr,$low,$high);
// 在元素分区之前
// 对数组进行排序
quickSort($arr,$low,$pi-1);
// 在分区元素之后
quickSort($arr,$pi+1,$high);
}
}
$arr= array(10,7,8,9,1,5);
$N=count($arr);
// 调用函数
quickSort($arr,0,$N-1);
echo "Sorted Array:\n";
for($i=0;$i<$N;$i++)
{
echo $arr[$i]. " ";
}
输出
排序数组:[1 5 7 8 9 10]
快速排序的复杂度分析:
时间复杂度:
- 最佳情况:Ω (N log (N)) 快速排序的最佳情况发生在每一步选择的主元将数组分成大致相等的两半时。 在这种情况下,算法将进行平衡分区,从而实现高效排序。
- 平均情况 : θ ( N log (N)) 快速排序的平均情况性能在实践中通常非常好,使其成为最快的排序算法之一。
- 最坏情况:O(N2) 当每一步的枢轴始终导致高度不平衡的分区时,快速排序的最坏情况就会发生。当数组已经排序并且枢轴始终被选为最小或最大元素时。为了缓解最坏的情况,使用了各种技术,例如选择一个好的主元(例如,三的中位数)并使用随机算法(随机快速排序)在排序之前对元素进行混洗。
- 辅助空间 :如果不考虑递归栈空间,O(1)。如果我们考虑递归堆栈空间,那么在最坏的情况下,快速排序的时间复杂度可能是 O ( N )。
快速排序的优点:
- 它是一种分而治之的算法,可以更轻松地解决问题。
- 它在大型数据集上非常有效。
- 它的开销很低,因为它只需要少量的内存即可运行。
快速排序的缺点:
- 它的最坏情况时间复杂度为 O(N^2^),当主元选择不当时就会发生这种情况。
- 对于小数据集来说这不是一个好的选择。
- 它不是稳定的排序,这意味着如果两个元素具有相同的键,在快速排序的情况下,它们的相对顺序将不会保留在排序的输出中,因为这里我们根据枢轴的位置交换元素(不考虑它们的原始位置)。