深入理解JS中的排序

在JavaScript开发中,排序是一项基础而重要的操作。本文将探讨JavaScript中几种常见的排序算法,包括它们的原理、实现方式以及适用场景。

1、冒泡排序

1.1、原理

通过比较相邻两个数的大小,交换位置排序:如果后一个数比前一个数小,则交换两个数的位置,重复这个过程,直到所有的数据按照升序排列。

1.2、代码实现

采用两层嵌套循环的方案,外层循环控制比较的轮数,内层循环用于比较相邻数据和交换位置

let Arr = [2, 5, 3, 7, 9, 1, 0, 6, 23, 12]

/**
 * 冒泡排序:
 * 时间复杂度O(n^2)
 * 原理:比较相邻的元素,如果第一个比第二个大,就交换它们两个
 * 注意:冒泡排序是原地排序算法,会改变原数组中元素顺序
 * @param {*} arr 
 * @returns 
 */
const bubbleSort = (arr) => {
    let len = arr.length;
    if (len <= 1) return arr;

    for (let i = 0; i < len; i++) {
        // 提前退出冒泡循环的标志位
        let swappend = false;
        for (let j = 0; j < len - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                const temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swappend = true;
            }
        }

        // 没有数据交换,提前退出
        if (!swappend) {
            break;
        }
    }
    return arr;
}

// 测试用例
console.log(bubbleSort(Arr));
// [0, 1, 2, 3, 5, 6, 7, 9, 12, 23]

2、快速排序

2.1、原理

采用二分法进行分区,再结果递归进行排序,具体操作如下:

  1. 选择基准元素(Pivot):从数组中随机选择一个元素作为基准值;
  2. 基于基准元素进行分区:比基准元素小的放到左边分区,比基准元素大的放到右侧分区;
  3. 对分区进行递归操作
2.2、代码实现
/**
 * 快速排序:
 * 时间复杂度O(nlogn)
 * 原理:采用二分法,取出中间的元素(基准-pivot),让小于基准的放到左边,大于基准的放到右边进行分区;然后递归的对左右两个分区再次进行二分法操作,进行快速排序
 * @param {*} arr 
 * @returns 
 */
const quickSort = (arr) => {
    if (arr.length <= 1) {
        return arr;
    }

    const pivotIndex = Math.floor(arr.length / 2);
    const pivot = arr.splice(pivotIndex, 1)[0];
    let left = [];
    let right = [];
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    return quickSort(left).concat([pivot], quickSort(right));
}

3、选择排序

3.1、原理

选择排序的原理很简单,它分为以下几个步骤:

  1. 查找最小(或最大)元素:在未排序的序列中找到最小(或最大)的元素。
  2. 交换位置:将找到的最小(或最大)元素和序列的第一个元素交换位置,如果最小元素就是序列的第一个元素,则位置保持不变。
  3. 重复步骤:对剩余未排序的序列重复上述两个步骤。

通过n-1次上述操作,就能完成整个序列的排序。具体来说,第一次从n个数据中选出最小的,放在第一个位置;第二次从剩下的n-1个数据中选出最小的,放在第二个位置;以此类推,直到整个序列变成有序序列。

3.2、代码实现
/**
 * 选择排序
 * 时间复杂度:O(n^2)
 * 原理:每次找到最小值,放到已排序数组的末尾;然后继续从剩余未排序数组中找到最小值,放到已排序数组的末尾
 * 说明:选择排序是原地排序算法,会改变原数组中元素顺序
 * @param {*} arr 
 * @returns 
 */
const selectionSort = (arr) => {
    const len = arr.length;
    if (len <= 1) {
        return arr
    }

    for (let i = 0; i < arr.length; i++) {
        let minIndex = i;
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex !== i) {
            const temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
    return arr;
}

4、归并排序

4.1、原理

归并排序的原理基于分治法(Divide and Conquer)策略,它包括以下三个步骤:

  1. 分解:将待排序的n个元素分成各含n/2个元素的子序列。
  2. 解决:使用归并排序递归地排序两个子序列。
  3. 合并:合并两个已排序的子序列以产生已排序的答案。

具体来说,归并排序的工作流程如下:

  • 递归地分解:从中间将数组分成两半,直到每个子数组只包含一个元素,因为单个元素被认为是已排序的。
  • 合并过程:然后开始合并这些子数组,以创建排序好的数组。这个过程是通过比较每个子数组的最前面的元素来完成的,选择两者中较小的那个放入新数组中,并移动所选元素所在数组的指针(或索引),直到所有子数组的元素都被合并成一个完整的排序数组。
4.2、代码实现
/**
 * 归并排序:
 * 时间复杂度:O(nlogn)
 * 原理:采用分治法,将数组分为左右两个数组,然后递归的对左右两个数组进行归并排序,最后合并两个有序数组
 * 说明:归并排序是稳定,不会改变原有数组
 * 详细步骤:
 *  1、递归的将数组分为左右两个数组,直到数组长度为1
 *  2、合并两个有序数组
 * @param {*} arr 
 * @returns 
 */
const mergeSort = (arr) => {
    if (arr.length <= 1) {
        return arr;
    }
    const middle = Math.floor(arr.length / 2);
    const left = arr.slice(0, middle);
    const right = arr.slice(middle);

    return merge(mergeSort(left), mergeSort(right));
}

const merge = (left, right) => {
    let result = [];
    let leftIndex = 0;
    let rightIndex = 0;

    while (leftIndex < left.length && rightIndex < right.length) {
        if (left[leftIndex] < right[rightIndex]) {
            result.push(left[leftIndex]);
            leftIndex++;
        } else {
            result.push(right[rightIndex]);
            rightIndex++;
        }
    }

    return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
}

5、上述排序算法的优缺点

5.1. 冒泡排序
  • 时间复杂度:平均和最坏情况为O(n^2),最好情况为O(n)(已经排序的情况)。
  • 空间复杂度:O(1),是原地排序算法。
  • 特点:实现简单,但效率较低,适用于小数据集的排序。
  • 工作原理:通过重复遍历待排序数组,比较相邻元素,若顺序错误则交换,直到整个数组排序完成。
5.2. 快速排序
  • 时间复杂度:平均和最好情况为O(n log n),最坏情况为O(n^2)(但可以通过选择合适的枢轴改进)。
  • 空间复杂度:O(log n),因为递归调用的栈空间。
  • 特点:效率高,是最常用的排序算法之一,但最坏情况下效率较低。
  • 工作原理:选择一个元素作为基准,将数组分为两部分,左边都比基准小,右边都比基准大,然后对这两部分递归地进行快速排序。
5.3. 选择排序
  • 时间复杂度:平均、最好和最坏情况都为O(n^2)。
  • 空间复杂度:O(1),是原地排序算法。
  • 特点:实现简单,但效率较低,不适合大数据集。
  • 工作原理:遍历数组,每次从未排序的部分选出最小(或最大)的元素,放到已排序部分的末尾。
5.4. 归并排序
  • 时间复杂度:平均、最好和最坏情况都为O(n log n)。
  • 空间复杂度:O(n),需要额外的存储空间。
  • 特点:效率高,稳定排序,适用于大数据集,但需要额外的内存空间。
  • 工作原理:将数组分成两半,对每一半递归地应用归并排序,然后将排序好的两半合并成一个有序数组。

总结

  • 对于小数据集,冒泡排序和选择排序简单但效率不高。
  • 快速排序在大多数情况下提供了很好的性能,是通用的排序算法之一。
  • 归并排序提供稳定的排序性能,特别适合于大数据集,但需要额外的内存空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Small_Teemo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值