核心内容摘要
“520886”的浪漫密码:不止是数字,更是心动的证明
快速排序是一种基于分治的排序算法它选择一个元素作为枢轴并通过将该枢轴置于排序后的数组中正确位置来划分。
该算法主要包含三个步骤选择枢轴从数组中选择一个元素作为枢轴。
枢轴的选择可以有所不同例如第一元素、最后元素、随机元素或中位数。
数组划分重新排列数组围绕枢轴。
划分后所有小于枢轴的元素位于其左侧所有大于枢轴的元素位于其右侧。
递归调用递归地将相同进程应用于两个分区子数组。
基础情况当子数组中只剩一个元素时递归停止因为单个元素已被排序。
枢轴选择选择枢轴有很多不同的选择。
始终选择第一个或最后一个元素作为枢轴。
下面的实现选择了最后一个元素作为枢轴。
这种方法的问题在于最坏的情况往往是数组已经排序好。
随机选一个元素作为枢轴。
这是一种首选方法因为它没有最坏情况发生的模式。
选择中位数元素是枢轴。
从时间复杂度角度看这是一种理想的方法因为我们可以在线性时间内找到中位数且配分函数总是将输入数组分为两半。
但平均来说需要更多时间因为中位数的发现常数很高。
划分算法快速排序中的密钥进程是一个分区。
有三种常见的划分算法。
所有这些算法的时间复杂度均为On。
朴素划分这里我们创建数组的副本。
先放所有较小的元素然后放更大的。
最后我们将临时数组复制回原始数组。
这需要 On 额外的空间。
洛穆托分区本文中使用了该分区。
这是一个简单的算法我们跟踪较小元素的索引并不断交换。
本文中我们使用它因为它非常简单。
霍尔分区这是所有分区中最快的。
这里我们从两侧遍历数组并在数组未分割的情况下不断交换左侧较大元素与右侧较小元素。
详情请参阅Hoare对Lomuto的对决。
Lomuto划分算法的工作原理及示意图逻辑很简单我们从最左边的元素开始并跟踪较小或相等元素的索引作为 i。
在遍历过程中如果发现一个较小的元素我们会将当前元素与 arr[i] 交换。
否则我们忽略当前元素。
重定向图标让我们通过以下示例来理解分区算法的工作原理快速排序算法示意在前一步我们研究了分区过程如何根据所选枢轴重新排列数组。
接下来我们递归地将同样的方法应用到枢轴左右两个较小的子数组 。
每次我们都选择新的枢轴点并再次划分数组。
这个过程会持续直到只剩下一个元素并且总是被排序。
一旦每个元素都处于正确位置整个数组就被排序。
下图展示了递归方法如何调用枢轴左右两个较小子数组import java.util.Arrays; class GfG { // partition function static int partition(int[] arr, int low, int high) { // choose the pivot int pivot arr[high]; // index of smaller element and indicates // the right position of pivot found so far int i low - 1; // traverse arr[low..high] and move all smaller // elements to the left side. Elements from low to // i are smaller after every iteration for (int j low; j high - 1; j) { if (arr[j] pivot) { i; swap(arr, i, j); } } // Move pivot after smaller elements and // return its position swap(arr, i 1, high); return i 1; } // swap function static void swap(int[] arr, int i, int j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; } // the QuickSort function implementation static void quickSort(int[] arr, int low, int high) { if (low high) { // pi is the partition return index of pivot int pi partition(arr, low, high); // recursion calls for smaller elements // and greater or equals elements quickSort(arr, low, pi -
; quickSort(arr, pi 1, high); } } public static void main(String[] args) { int[] arr {10, 7, 8, 9, 1, 5}; int n arr.length; quickSort(arr, 0, n -
; for (int val : arr) { System.out.print(val ); } } }输出1 5 7 8 9 10快速排序的复杂度分析时间复杂性最佳情况Ωn log n当枢轴元素将数组分为两个相等的一半时发生。
平均情况 θn log n平均而言枢轴将数组分为两部分但不一定相等。
最坏情况On²当始终选择最小或最大元素作为枢轴时例如已排序的数组。
辅助空间最坏情况由于不平衡划分导致递归树倾斜需要调用栈大小为 On 的 On。
最佳情况由于平衡分区Olog n 生成了一个具有调用栈大小为 Olog n 的平衡递归树。
详情请参阅快速排序的时间与空间复杂性分析。
快速排序的优势它是一种分而治之的算法使解决问题变得更容易。
它在大数据集上效率很高。
它开销低只需少量内存即可运行。
它对缓存友好因为我们在同一数组上进行排序且不会将数据复制到任何辅助数组。
在不需要稳定性的情况下是处理大数据的最快通用算法。
它是尾部递归的因此所有尾调用优化都是可以完成的。
快速排序的缺点其最坏时间复杂度为 On2当枢轴选择不当时就会发生这种情况。
对于小型数据集来说它并不是一个好的选择。
它不是稳定排序意味着如果两个元素键相同快速排序时它们的相对顺序不会在排序输出中保持因为这里我们根据枢轴的位置交换元素不考虑它们的原始位置。
快速排序的应用高效地在内存中排序大型数据集。
用于库的排序函数如 C stdsort 和 Java Arrays.sort 用于原语。
在数据库中整理记录以加快搜索速度。
需要排序输入的算法中的预处理步骤例如二分搜索、两点技术。
使用快速选择Quicksort 的一种变体找到第 k 个最小/最大的元素。
基于多个键自定义比较器排序对象数组。
数据压缩算法如霍夫曼编码预处理。
图形学和计算几何例如凸包算法。
编程资源 https://pan.quark.cn/s/7f7c83756948 更多资源 https://pan.quark.cn/s/bda57957c548