fucking-algorithm icon indicating copy to clipboard operation
fucking-algorithm copied to clipboard

快速排序详解及应用 :: labuladong的算法小抄

Open utterances-bot opened this issue 3 years ago • 9 comments

文章链接点这里:快速排序详解及应用

评论礼仪 见这里,违者直接拉黑。

utterances-bot avatar Mar 21 '22 01:03 utterances-bot

while (i < hi && nums[i] <= pivot) {
    i++;
    // 此 while 结束时恰好 nums[i] > pivot
}

不知道我的理解是否正确

对于数组 [5,1,2,3,4] 排序时,经过这个循环,i 指向 4 ,并不满足 nums[i] > pivot,但是会由 if (i >= j) break; 终止 swap,结果是一样的

对于数组 [5,1,2,3,4,8] 排序时,就没问题了

试了一下,这里使用 i <= hi 也能AC,感觉这里之所以用 i < hi 是因为能够减少一次循环,因为hi元素的大小会在下面的j循环得到对比

对于数组 [5,1,2,3,4] 排序时,使用 i <= hi ,经过上面的循环i会指向4后面,但是依然会由 if (i >= j) break; 终止 swap,结果是一样的

j > lo 是因为 lo 元素就是 pivot,无须比较

wy-luke avatar Apr 05 '22 07:04 wy-luke

@wy-luke 给你的思考点赞!关于你说的 使用 i <= hi 也能AC,确实是这样的,我简单说下我的分析:

这里的关键是控制 j 指针的位置,因为外层 while 结束之后需要把 pivot 换到 nums[j] 的位置上去。你对 i 的边界情况有微调,只要不影响 j 的位置,同时能够保证触发 if (i >= j) break; 的终止条件,则最终的结果都是正确的。

labuladong avatar Apr 06 '22 08:04 labuladong

@labuladong 谢谢东哥!

wy-luke avatar Apr 06 '22 08:04 wy-luke

nice

cheng-miao avatar Apr 16 '22 11:04 cheng-miao

  1. 排序数组 javascript version
var sortArray = function(nums) {
    // 为了避免出现耗时的极端情况 先随机打乱数组
    randomShuffle(nums);
    // 排序整个数组(原地修改)
    sort(nums, 0, nums.length - 1);
    return nums;

    // 排序数组nums
    function sort(nums, lo, hi) {
        if (lo >= hi) {
            return;
        }

        /****** 前序位置 ******/
        // 对nums[lo...hi]进行切分
        // 使得nums[lo...p-1] <= nums[p] < nums[p+1..hi]
        let p = partition(nums, lo, hi);
        /*********************/

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

    // 对nums[lo...hi]进行切分
    function partition(nums, lo, hi) {
        // 取第一个位置的元素作为基准元素
        let pivot = nums[lo];
        // 这里把left, right定义为开区间
        // 同时定义 [lo, left) <= pivot; (right, hi] > pivot
        // 之后都要正确维护这个边界区间的定义
        let left = lo + 1;
        let right = hi;
        
        // 当left > right时结束循环 以保证区间[lo, hi]都被覆盖
        while(left <= right) {
            while(left < hi && nums[left] <= pivot) {
                left++;
                // 此while循环结束时 恰好 nums[left] > pivot
            }
            while(right > lo && nums[right] > pivot) {
                right--;
                // 此while循环结束时 恰好 nums[right] <= pivot
            }
            // 此时[lo, left) <= pivot && (right, hi] > pivot

            if (left >= right) {
                break;
            }
            swap(nums, left, right)
        }
        // 将pivot放到合适的位置 即pivot左边元素较小 右边元素较大
        swap(nums, lo, right);
        return right;
    }

    // 随机打乱数组
    function randomShuffle(nums) {
        let len = nums.length;
        for (let i; i < len; i++) {
            // 随机生成[i, n - i]的随机数
            let index = i + Math.floor(Math.random * (len - i));
            swap(nums, i, index);
        }
    }

    // 原地交换数组中的两个元素
    function swap(nums, i, j) {
        let temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
};
  1. 数组中的第K个最大元素 javascript version
var findKthLargest = function(nums, k) {
    // 转化成升序排名第targetIndex的元素
    let targetIndex = nums.length - k;
    let start = 0;
    let end = nums.length - 1;
    /** 在nums[start, end]中选择一个分界点 */
    let index = partition(nums, start, end);

    while (index != targetIndex) {
        if (index > targetIndex) {
            // 第k大的元素在nums[start, index - 1]中
            end = index - 1;
        } else {
            // 第k大的元素在nums[index + 1, end]中
            start = index + 1;
        }
        // 在nums[start, end]中选一个分界点
        index = partition(nums, start, end);
    }
    // 找到第k大的元素
    return nums[index];
}

/****** 对nums[start, end]进行切分 ******/
function partition(arr, startIndex, endIndex) {
    // 取第一个位置的元素作为基准元素
    let pivot = arr[startIndex];
    // 设置一个mark指针指向数组的起始位置 最终mark指针代表小于基准元素的区域边界
    let mark = startIndex;
    for (let i = startIndex+1; i <= endIndex; i++) {
        if (arr[i] < pivot) {
            mark++;
            // 交换两个元素
            [arr[mark], arr[i]] = [arr[i], arr[mark]];
        }
    }
    // 更新分界点的值
    arr[startIndex] = arr[mark];
    arr[mark] = pivot;
    return mark;
}

David-qiuwenhui avatar Apr 20 '22 08:04 David-qiuwenhui

东哥,算时间复杂度那,是等比数列,不是等差数列

wercyle avatar May 31 '22 02:05 wercyle

@wercyle 感谢指出,是我写错了,已修复~

labuladong avatar May 31 '22 08:05 labuladong

这个分区容易理解点,有点双指针技巧含义,选最后一个元素作为分区点,指针 i 表示比分区值小的元素应该放的位置,指针 j 只用来遍历。当 j 遍历到比分区值小的元素时,放到指针 i 的位置(通过交换实现)。当 j 遍历完时,[lo, i - 1] 都是比分区值小的元素,[i, hi - 1] 都是比分区值大的元素,最后交换一下分区值和 i 所指向的元素便实现了 pivot 左边都是比它小的元素,右边都是比它大的元素。

private static int partition(int[] arr, int lo, int hi) {
    int pivot = arr[hi];
    int i = lo, j = lo;
    while (j < hi) {
        if (arr[j] < pivot) {
            swap(arr, i, j);
            i++;
        }
        j++;
    }
    swap(arr, i, hi);
    return i;
}

615lyw avatar Jul 21 '22 03:07 615lyw

笔记: partition 里面有while(i <= j) {,等号的目的是解决array的长度只有2的edge case,其中一个被抓去做了pivot,另一个必须得验证一下是不是比pivot大,不是的话还是要swap一下。 如果长度只有1,那么进不了这个while循环

quick select里面还有一个while(i <= j) {, 等号的目的是解决array长度只有1的edge case。理论上应该返回0,如果不加等号会返回-1。

Goolloo avatar Aug 08 '22 07:08 Goolloo

东哥,现在912题快速排序算法会超时,应该是加入了输入是基本有序的数组case导致的

suifengqh avatar Sep 11 '22 03:09 suifengqh