手撕算法(7)——sort

14 阅读3分钟

sort 函数的工作原理

通过比较数组中两个元素的大小来决定它们的位置。比较函数 (compare) 会被传入两个参数 a 和 b,它的返回值决定了这两个元素的顺序:

如果返回值小于 0,那么 a 会被排在 b 之前。 如果返回值等于 0,那么 a 和 b 的顺序保持不变。 如果返回值大于 0,那么 a 会被排在 b 之后。

用冒泡排序实现 sort 函数

function myBubbleSort(arr, compare) {
    const n = arr.length;
    // 外循环:未排序区间 [0, i]
    for (let i = n - 1; i > 0; i--) {
        // 内循环:将未排序区间的最大元素换到区间最右端
        for (let j = 0; j < i; j++) {
            // 使用比较函数来决定元素是否需要交换位置
            if (compare(arr[j], arr[j + 1]) > 0) {
                // 交换元素
                const tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
    return arr;
}

// 比较函数示例:升序排序
function ascending(a, b) {
    return a - b;
}

// 使用自定义排序函数对数组进行排序
const arr = [5, 3, 8, 4, 2];
const sortedArr = myBubbleSort(arr, ascending);

console.log(sortedArr); // 输出: [2, 3, 4, 5, 8]

用快速排序实现 sort 函数

快速排序原理

快速排序是一种“分而治之”的算法。它通过选择一个“基准”(pivot)元素,将数组分成两个子数组,分别是小于基准的部分和大于基准的部分。然后递归地对这两个子数组进行排序,最终将排序好的子数组合并起来。

function myQuickSort(arr, compare) {
    // 如果数组只有一个元素或为空,则直接返回
    if (arr.length <= 1) {
        return arr; 
    }
    
    // 选择第一个元素作为基准,也可以任选一个元素
    const pivot = arr[0]; 
    
    // 创建两个空数组,分别存放比基准小、比基准大的元素
    const left = [];
    const right = [];

    // 从第二个元素开始遍历数组(因为pivot是arr[0]),将元素按照大小放入left或right数组中
    for (let i = 1; i < arr.length; i++) {
        // if (i === pivot) continue; // 如果不是arr[0],i从0开始,需要跳过基准元素
        if (compare(arr[i], pivot) < 0) {
            left.push(arr[i]); // 小于基准的元素放在左边
        } else {
            right.push(arr[i]); // 大于或等于基准的元素放在右边
        }
    }

    // 递归地对left和right数组进行排序,并将结果合并成一个有序数组并返回
    return myQuickSort(left, compare).concat(pivot, myQuickSort(right, compare));
    // return [...myQuickSort(left, compare), pivot, ...myQuickSort(right, compare)];
}

// 比较函数示例:升序排序
function ascending(a, b) {
    return a - b;
}

// 使用自定义快速排序函数对数组进行排序
const array = [5, 3, 8, 4, 2];
const sortedArray = myQuickSort(array, ascending);

console.log(sortedArray); // 输出: [2, 3, 4, 5, 8]
代码解释

递归基准: 当数组的长度小于等于 1 时,直接返回该数组,因为它已经是有序的。
选择基准: 我们选择数组中间的元素作为基准(pivot)。
分区操作: 遍历数组,基于比较函数的结果,将小于基准的元素放入 left 数组,大于等于基准的元素放入 right 数组。
递归排序: 递归地对 left 和 right 数组进行排序,并将结果与基准元素合并起来,形成一个新的排序数组。

为什么快速排序更快?
快速排序在平均情况下的时间复杂度为 O(n log n),这使它比冒泡排序的 O(n^2) 更高效。它通过“分而治之”的策略,将问题规模减小,从而在大多数情况下表现出更好的性能 ,特别适合处理较大规模的数组。