从二叉树角度来理解快速排序

快速排序就是个二叉树的前序遍历

                                                                                        ——————labuladong

快速排序的逻辑

快速排序是先将一个元素排好序,然后再将剩下的元素排好序

若要对 nums[lo..hi] 进行排序,我们先找一个分界点 p,通过交换元素使得 nums[lo..p-1] 都小于等于 nums[p],且 nums[p+1..hi] 都大于 nums[p],然后递归地去 nums[lo..p-1] 和 nums[p+1..hi] 中寻找新的分界点,最后整个数组就被排序了。

从二叉树的视角,我们可以把子数组 nums[lo..hi] 理解成二叉树节点上的值,srot 函数理解成二叉树的遍历函数。

partition 函数每次都将数组切分成左小右大两部分,最后形成的这棵二叉树是一棵二叉搜索树

但谈到二叉搜索树的构造,那就不得不说二叉搜索树不平衡的极端情况,极端情况下二叉搜索树会退化成一个链表,导致操作效率大幅降低。所以我们要对初始数组进行随机性处理

快速排序的代码框架

void sort(int[] nums, int lo, int hi) {
    /****** 前序遍历位置 ******/
    // 通过交换元素构建分界点 p
    int p = partition(nums, lo, hi);
    /************************/

    sort(nums, lo, p - 1);
    sort(nums, p + 1, hi);
}

我们与二叉树的前序遍历代码框架进行对比:

/* 二叉树遍历框架 */
void traverse(TreeNode root) {
    if (root == null) {
        return;
    }
    /****** 前序位置 ******/
    print(root.val);
    /*********************/
    traverse(root.left);
    traverse(root.right);
}

快速排序的代码实现

class Quick {
    public static void sort(int[] nums) {
        // 为了避免出现耗时的极端情况,先随机打乱数组元素顺序
        shuffle(nums);
        // 排序整个数组(原地修改)
        sort(nums, 0, nums.length - 1);
    }

    // 私有的递归排序函数,使用快速排序算法对数组进行排序 
    private static void sort(int[] nums, int lo, int hi) {
        if (lo >= hi) {
            return; // 递归结束条件,当 lo 大于等于 hi 时,子数组已经有序
        }
        // 对 nums[lo..hi] 进行切分
        // 使得 nums[lo..p-1] <= nums[p] < nums[p+1..hi]
        int p = partition(nums, lo, hi); // 获取切分点 p

        // 对切分点左右两边的子数组分别进行递归排序
        sort(nums, lo, p - 1);
        sort(nums, p + 1, hi);
    }

    // 切分函数,用于确定切分点并重新排列数组元素
    private static int partition(int[] nums, int lo, int hi) {
        int pivot = nums[lo]; // 选择第一个元素作为切分元素
        int i = lo + 1, j = hi; // 定义两个指针 i 和 j

        // 使用双指针将数组划分为两部分,一部分小于等于pivot,一部分大于pivot
        while (i <= j) {
            while (i < hi && nums[i] <= pivot) {
                i++; // 移动左指针直到找到一个大于pivot的元素
            }
            while (j > lo && nums[j] > pivot) {
                j--; // 移动右指针直到找到一个小于等于pivot的元素
            }
            // 交换左右指针所指向的元素,保证左边小于等于pivot,右边大于pivot
            if (i >= j) {
                break; // 当左右指针相遇时退出循环
            }
            swap(nums, i, j); // 交换 nums[i] 和 nums[j]
        }
        // 将pivot放到合适的位置,即使得左边元素小于等于pivot,右边元素大于pivot
        swap(nums, lo, j); // 将切分元素放到最终位置
        return j; // 返回切分元素的下标
    }

    // 洗牌算法,将输入的数组随机打乱
    private static void shuffle(int[] nums) {
        Random rand = new Random(); // 创建随机数生成器
        int n = nums.length;
        for (int i = 0 ; i < n; i++) {
            // 生成 [i, n - 1] 的随机数
            int r = i + rand.nextInt(n - i); // 生成 [i, n - 1] 范围内的随机数
            swap(nums, i, r); // 将第i个元素与第r个元素交换
        }
    }

    // 原地交换数组中的两个元素
    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i]; // 临时保存 nums[i]
        nums[i] = nums[j];  // 将 nums[j] 放到 nums[i] 的位置
        nums[j] = temp;      // 将临时保存的 nums[i] 放到 nums[j] 的位置
    }
}

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值