【快速选择】-进阶

快速选择算法 (Quick Select) 详解

这段代码实现的是 快速选择(Quick Select)算法,它是一种在 无序数组 中寻找 第 k 小的元素 的算法。与快速排序类似,快速选择也基于 分治法(Divide and Conquer),但它并不需要对整个数组进行排序,只需进行部分排序以找到目标元素。

该实现的目标是找到数组中的 第 k 小的元素,而不需要排序整个数组。通过修改标准的快速排序,使其只关心数组的分区而忽略其他部分,从而优化了性能。


1. quickFind 方法

public static int quickFind(int[] array, int aim) {
    return quickFindCore(array, aim, 0, array.length - 1);
}
  • 功能:这是快速选择的入口方法。它接收两个参数:array 是待查找的数组,aim 是目标位置,即我们希望找到数组中第 aim 小的元素。
  • quickFindCore 是递归调用的核心方法,它会在指定的左右区间 [left, right] 内进行查找,直到找到目标元素。

2. quickFindCore 方法(核心递归)

public static int quickFindCore(int[] array, int aim, int left, int right) {
    if (left >= right) {
        return array[left];
    }
    int pivotIndex = partition(array, left, right);
    
    if (pivotIndex > aim) {
        return quickFindCore(array, aim, left, pivotIndex - 1);
    } else if (pivotIndex < aim) {
        return quickFindCore(array, aim, pivotIndex + 1, right);
    } else {
        return array[pivotIndex];
    }
}
  • 功能:这个方法是快速选择的核心,采用了递归方式来定位目标元素。
  • 递归基准条件:如果 left >= right,表示数组已经缩小到只有一个元素,返回该元素。
  • 分区操作:通过调用 partition 方法,将数组分成两部分并返回分区的枢轴位置 pivotIndex
  • 递归选择方向:根据 pivotIndex 的位置来决定递归的方向:
    • 如果 pivotIndex > aim,说明目标元素在左侧子数组中,递归处理左侧子数组。
    • 如果 pivotIndex < aim,说明目标元素在右侧子数组中,递归处理右侧子数组。
    • 如果 pivotIndex == aim,说明找到了目标元素,直接返回。

3. partition 方法(分区操作)

public static int partition(int[] array, int left, int right) {
    int pivot = array[right]; // 枢轴是当前子数组的最后一个元素
    int leftIndex = left;
    int rightIndex = right - 1;
    
    while (true) {
        // 左指针移动
        while (array[leftIndex] <= pivot && leftIndex < right) {
            leftIndex++;
        }
        // 右指针移动
        while (array[rightIndex] > pivot && rightIndex > 0) {
            rightIndex--;
        }

        if (leftIndex >= rightIndex) {
            break;
        } else {
            swap(array, leftIndex, rightIndex);
        }
    }

    swap(array, leftIndex, right);
    return leftIndex;
}
  • 功能:分区操作用于将数组根据枢轴元素进行划分,使得左侧部分小于枢轴,右侧部分大于枢轴,最终返回枢轴元素的索引。
  • 枢轴选择:该实现选择数组的最后一个元素作为枢轴。
  • 双指针法
    • leftIndex 从数组的左侧开始,寻找比枢轴大的元素。
    • rightIndex 从右侧开始,寻找比枢轴小的元素。
    • leftIndex 小于 rightIndex 时,交换这两个元素,直到指针交叉。
  • 交换枢轴:在分区完成后,将枢轴与 leftIndex 位置的元素交换,确保枢轴元素在其最终位置。

4. swap 方法(交换数组元素)

public static void swap(int[] array, int index1, int index2) {
    int temp = array[index1];
    array[index1] = array[index2];
    array[index2] = temp;
}
  • 功能:交换数组中两个指定位置的元素。通过一个临时变量 temp 保存一个元素的值,然后交换这两个元素。

5. main 方法(测试)

public static void main(String[] args) {
    int[] array = {72, 77, 48, 17, 71, 2, 25, 97, 82, 5, 2, 18, 15, 57, 7, 48, 93, 47, 38, 74, 18, 93, 98, 41, 54, 4, 47, 4, 63, 76};
    System.out.println("raw: " + Arrays.toString(array));
    // 目标是倒数第 6 个元素
    int result = quickFind(array, array.length - 6);
    System.out.println("result: " + result);
}
  • 功能:在 main 方法中,我们初始化一个数组 array,并使用 quickFind 方法查找数组中倒数第 6 个元素(即 array.length - 6)。
  • quickFind 返回数组中第 aim 小的元素,并打印出结果。

快速选择的工作原理:

  1. 分区:首先将数组分区,选择一个枢轴元素,分成两部分,左侧部分小于枢轴,右侧部分大于枢轴。
  2. 递归:根据目标位置 aim 与枢轴位置的比较决定递归的方向:
    • 如果目标位置在枢轴左边,则只对左侧子数组递归。
    • 如果目标位置在枢轴右边,则只对右侧子数组递归。
    • 如果目标位置等于枢轴位置,则返回枢轴元素,即目标元素。
  3. 停止条件:当递归的子数组只剩一个元素时,返回该元素。

快速选择的时间复杂度:

  • 平均时间复杂度O(n),由于每次分区大约能将数组减半,且递归深度为 O(log n),但是不需要对整个数组进行排序,因此可以在 O(n) 时间内找到第 k 小的元素。
  • 最坏时间复杂度O(n^2),在最坏的情况下(例如每次选择的枢轴都是最小或最大元素),递归将遍历整个数组。类似于快速排序的最坏情况。

新手需要了解的关键点:

  1. 快速选择与快速排序的区别

    • 快速排序:对整个数组进行排序,时间复杂度 O(n log n)
    • 快速选择:只关心找到第 k 小的元素,时间复杂度 O(n),无需对数组完全排序。
  2. 分区操作(Partition):这是快速排序和快速选择算法的核心。通过分区操作,我们能够确定某个元素的最终位置,而无需对数组进行完全排序。

  3. 递归:快速选择使用递归来逐步缩小查找范围,直到找到目标元素的位置。

  4. 双指针法:用于分区的双指针方法通过从两端扫描数组来寻找需要交换的元素。

  5. 枢轴元素:枢轴元素是快速排序和快速选择中的核心,通过它来分区数组。选取合适的枢轴元素可以提高算法性能。


示例输出:

假设我们希望找到倒数第 6 个元素。

raw: [72, 77, 48, 17, 71, 2, 25, 97, 82, 5, 2, 18, 15, 57, 7, 48, 93, 47, 38, 74, 18, 93, 98, 41, 54, 4, 47, 4, 63, 76]
result: 57

总结:

快速选择算法通过改进快速排序的思想,只关注数组的一部分,能够在 O(n) 的时间内找到数组中的第 k 小元素,性能上比排序整个数组更优。它通过递归和分区来缩小查找范围,是一种高效的元素查找算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值