快速排序算法详解:
一:理论基础
快速排序(Quicksort)是一种常用的排序算法,它是一种分治算法,通过将问题分解成更小的子问题来解决整个问题。快速排序的基本思想是选择一个基准元素,然后将数组中小于基准元素的元素移到基准元素的左边,大于基准元素的元素移到右边,然后递归地对左右两个子数组进行排序。
二:快速排序的基本思想:
通过一趟排序将待排序列分隔成独立的两部分,其中一部分记录的元素均比另一部分的元素小,则可分别对这两部分子序列继续进行排序,以达到整个序列有序。
三:算法步骤
以下是快速排序的基本步骤:
-
选择基准元素:从数组中选择一个元素作为基准(通常选择第一个元素、最后一个元素或者随机选择)。
-
划分:将数组中小于基准元素的元素移到基准元素的左边,大于基准元素的元素移到右边。通常使用两个指针来实现这一步骤,一个从数组的左端开始,一个从右端开始,然后它们向中间移动,直到它们相遇。
-
递归排序:递归地对划分后的左右两个子数组进行排序。
-
合并:无需合并,因为排序是原地进行的。
-
递归终止条件:当子数组的大小为0或1时,递归停止。
四:算法图解
五:手撕算法
分区方法采用:单边循环快排(lomuto 洛穆托分区方案)
知识拓展:洛穆托分区方案
洛穆托分区方案(Lomuto Partition Scheme)是快速排序算法中的一种常见的分区方法之一,由尼古拉斯·洛穆托(Nicolas Lomuto)于1991年提出。它相对简单,易于理解和实现。
该分区方案的基本思想是选择数组中的最后一个元素作为基准(pivot),然后将数组分成两部分:左边部分包含所有小于基准的元素,右边部分包含所有大于等于基准的元素。分区完成后,基准元素位于最终排序位置上。
以下是洛穆托分区方案的基本步骤:
-
选择基准元素:将数组中最后一个元素作为基准。
-
遍历数组:从数组的起始位置到倒数第二个元素,使用一个指针(通常称为
i
)遍历数组。 -
比较并交换:对于遍历过程中的每个元素,如果元素的值小于基准元素的值,则将其与另一个指针(通常称为
j
,初始值为数组起始位置)所指向的元素进行交换,并将j
向后移动一位。 -
最后交换:遍历完成后,将基准元素与
j
指向的元素进行交换,将基准元素放置到最终的位置上。 -
返回基准索引:返回基准元素的索引。
这个过程完成后,基准元素将位于排序后的最终位置,同时左边的元素都小于基准元素,右边的元素都大于等于基准元素。
洛穆托分区方案相对于其他分区方案(如霍尔分区方案)可能会在某些情况下导致不太均衡的划分,因此在实践中可能不如其他分区方案效率高,但其简单直观的特点使得它在教学和初学者理解快速排序时仍然是一种常用的方法。
public class QuickSort {
// 单边循环快排
public void quickSort(int[] nums) {
doQuickSort(nums, 0, nums.length - 1);
}
private void doQuickSort(int[] nums, int left, int right) {
if (left >= right) {
return;
}
// 分区
int p = partition(nums, left, right);
doQuickSort(nums, left, p - 1);
doQuickSort(nums, p + 1, right);
}
/**
* <h3>单边循环快排(lomuto 洛穆托分区方案)</h3>
* <p>核心思想:每轮找到一个基准点元素,把比它小的放到它左边,比它大的放到它右边,这称为分区</p>
* <ol>
* <li>选择最右元素作为基准点元素</li>
* <li>j 找比基准点小的,i 找比基准点大的,一旦找到,二者进行交换</li>
* <ul>
* <li>交换时机:j 找到小的,且与 i 不相等</li>
* <li>i 找到 >= 基准点元素后,不应自增</li>
* </ul>
* <li>最后基准点与 i 交换,i 即为基准点最终索引</li>
* </ol>
*/
private int partition(int[] nums, int left, int right) {
int pv = nums[right]; // 基准元素
int i = left; // 找比基准点大的值,如果找到大于等于基准点的值,不在移动
int j = left; // 找比基准点小的值
while (j <