前端排序算法-快速排序

120 阅读2分钟

快速排序曾被部分 v8 作为 Array.prototype.sort 的实现,快排是不稳定的排序,其平均复杂度为 O(NlogN) 若选定左侧作为基准值,升序排序的情况下,有以下实现,参看视频

排序思路

1、选定基准值,将比基准值小的放在其左侧,比基准值大的放在右侧: 右哨兵从右往左扫描找到比基准值小,左哨兵从左往右扫描找到比基准值大的,两者互换。

2、持续扫描直至二者相遇,将相遇处值与基准值互换。

3、分治,左侧右侧两个分区继续进行以上逻辑(通过递归实现),直到所有分区排序完毕。

            i 为相遇点,left 为左边界,right 为右边界
            quick_sort(arr, left, i - 1);
            quick_sort(arr, i + 1, right)

排序要点

  • 分治,递归
  • 注意边界条件,i<j
  • 注意需要从右到左先遍历

排序过程

给定原数组,排序主要过程如下:

          const arr=[30,40,60,10,20,50];
              /**
         * 基准值交换
         * [30,40,60,10,20,50];
         * 第一轮交换
         * 30,40,60,10,20,50
         *     ^        ^   
         * 第一轮交换结束
         * 30,20,60,10,40,50
         *     ^        ^
         * 第二轮交换
         * 30,20,60,10,40,50
         *        ^  ^      
         * 第二轮交换结束
         * 30,20,10,60,40,50
         *        ^  ^      
         * 第三轮交换,左右哨兵相遇,置换相遇位置与基准值
         * 30,20,10,60,40,50
         *        ^
         *        ^
         * 第三轮交换结束
         * 10,20,30,60,40,50
         *  ^     ^
         * 
         * 基准值换
         * 10,20
         * 第一轮交换
         * 10,20
         *  ^  ^
         * 左右哨兵相遇,置换相遇位置与基准值
         * 10,20
         * ^
         * ^
         * 第一轮交换结束
         * 10,20
         * 
         * 基准值交换
         * 0 1=>
         * 0 -1(left>right直接返回)
         * 1 1
         * 
         * 1 1=>
         * 1 0(left>right直接返回)
         * 2 1(left>right直接返回)
        */

排序实现

     const quick_sort = (arr, left, right) => {
            if (!arr.length || left > right) {
                console.log(`return left-${left}|right-${right}`);

                return;
            }
            console.log(`left-${left}|right-${right}`);
            console.log(`baseArr|${JSON.stringify(arr)}`);

            let base = arr[left];//基准值
            console.log(`base-${base}|baseIndex-${left}`);

            let i = left;
            let j = right;
            while (i < j) {
                console.log(`arr[i]-${arr[i]}|arr[j]-${arr[j]}`);
                // 因为是需要升序,基准值右侧需要比基准值大(包括等于),从右往左找比基准值小的,找到后暂停,等待与从左往右找到比基准值大的值交换
                while (arr[j] >= base && i < j) {
                    j--;
                }
                // 找到从左往右找到比基准值大的值,当 arr[i] 比基准值小(包括等于),则一直循环
                while (arr[i] <= base && i < j) {
                    i++;
                }
                console.log(`i-${i}|j-${j}`);
                if (i < j) {
                    // 交换左右,es6 解构赋值
                    [arr[i], arr[j]] = [arr[j], arr[i]];
                    // let temp=arr[i];
                    //  arr[i]=arr[j];
                    //  arr[j]=temp;
                }
                console.log(`inside ${JSON.stringify(arr)}`);

            }
            console.log(`i-${i}`);

            // 左右哨兵相遇,此时相遇位置值与基准值互换
            arr[left] = arr[i];
            arr[i] = base;
            console.log(`outside ${JSON.stringify(arr)}`);
            quick_sort(arr, left, i - 1);
            quick_sort(arr, i + 1, right)

        }
        quick_sort(arr, 0, arr.length - 1);
        console.log(arr);

先从右往左扫描的理由

     /**
         * 至于为什么需要先从右往左遍历,主要是先后顺序会影响升序降序的顺序,考虑这样的一个例子
         * 原数组:[3,7,4,5,8,6,2]
         * 设置基准值为左侧第一位3,
         * 
         * 若从左往右遍历,
         * 第一轮交换
         * 先寻找比基准值大的,找到7;后从右往左,找到比基准值小的2,
         * [3,7,4,5,8,6,2]
         *    ^         ^ 
         * 第一轮交换后数组
         * [3,2,4,5,8,6,7]
         *    ^         ^       
         * 第二轮交换
         * 先寻找比基准值大的,找到4;后从右往左,遇到4,停止寻找(即需满足i<j)
         * [3,2,4,5,8,6,7]
         *      ^
         *      ^
         * 此时进行基准值与边界值的交换
         * 第二轮交换后数组
         * [4,2,3,5,8,6,7]
         *  ^   ^
         * 此时可以看到,基准值左侧的部分数组降序了,不符合整体升序要求
         * 
         * 
         * 原数组:[3,7,4,5,8,6,2]
         * 若从右往左遍历,
         * 第一轮交换
         * 先寻找比基准值小的,找到2;后从左往右,找到比基准值大的7,
         * [3,7,4,5,8,6,2]
         *    ^         ^
         * 第一轮交换后数组
         * [3,2,4,5,8,6,7]
         *    ^         ^
         * 第二轮交换
         * 先寻找比基准值小的,找到2;后从左往右,遇到2,停止寻找(即需满足i<j)
         * [3,2,4,5,8,6,7]
         *    ^
         *    ^
         * 此时进行基准值与边界值的交换
         * 第二轮交换后数组
         * [2,3,4,5,8,6,7]
         *  ^ ^
         * 此时可以看到,基准值左侧的部分数组是升序,符合整体升序要求
        */

参考文档

标题链接
仓库地址gitee.com/zhu_zhi_kan…
讲解录屏www.bilibili.com/video/BV1SA…

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情