[javascript]排序算法--归并排序/基数排序

52 阅读4分钟

归并排序是一种有效的排序算法,采用分治法(Divide and Conquer)的思想来进行排序。其基本步骤包括:

  1. 分割(Divide) :将数组分成两个子数组,分别对两个子数组进行排序。
  2. 合并(Conquer) :将两个已经排序的子数组合并成一个有序的数组。

归并排序的时间复杂度为O(n log n),并且适用于大多数排序任务。

function mergeSort(arr) {
    if (arr.length <= 1) {
        return arr; // 如果数组只有一个元素,返回该数组
    }

    // 找到数组的中间位置
    const middleIndex = Math.floor(arr.length / 2);

    // 使用slice方法将数组分为左右两个子数组
    const leftArray = arr.slice(0, middleIndex);
    const rightArray = arr.slice(middleIndex);

    // 递归对左右子数组进行排序,并合并结果
    return merge(mergeSort(leftArray), mergeSort(rightArray));
}

function merge(leftArray, rightArray) {
    const sortedArray = []; // 用于存储合并后的有序数组
    let leftIndex = 0;
    let rightIndex = 0;

    // 同时遍历左右两个数组,比较元素并排序
    while (leftIndex < leftArray.length && rightIndex < rightArray.length) {
        if (leftArray[leftIndex] < rightArray[rightIndex]) {
            sortedArray.push(leftArray[leftIndex]);
            leftIndex++; // 左数组元素已使用,移动左指针
        } else {
            sortedArray.push(rightArray[rightIndex]);
            rightIndex++; // 右数组元素已使用,移动右指针
        }
    }

    // 将剩余的元素添加到有序数组中(如果有多余的元素)
    return sortedArray.concat(leftArray.slice(leftIndex)).concat(rightArray.slice(rightIndex));
}

// 使用例子
const unsortedArray = [38, 27, 43, 3, 9, 82, 10];
const sortedArray = mergeSort(unsortedArray);
console.log(sortedArray); // 输出: [3, 9, 10, 27, 38, 43

思路解释:

  • 分割:利用递归将数组不断地一分为二,直到每个数组只剩一个元素时停止分割。
  • 合并过程:在递归返回时,调用merge函数,该函数接受两个有序的子数组,并将它们合并成为一个有序数组。
  • 合并方法中的比较与合并:遍历左右数组,同时从数组头部开始对比,将较小的元素放入sortedArray中,这样保证合并后的数组依然有序。

通过这种递归的分割与合并,最终可以获得一个升序排列的数组。这种算法特别适合处理大规模数据,因为它的复杂度较低且比较稳定。

基数排序(Radix Sort)是一种非比较型的排序算法,它通过将数字分成不同的位(digits)进行排序。在排序过程中,基数排序通常采用“从最低位到最高位”(LSD, Least Significant Digit)的方法,一次处理每一位,并利用一个稳定的排序算法(如计数排序)进行排序。

基数排序的步骤:

  1. 找出最大的数字:确定待排序数组中数字的最大位数。
  2. 从最低位开始排序:对每个位进行排序,依次从最低位(单位)到最高位。
  3. 使用稳定的排序算法:在每一位上应用稳定的排序算法(如计数排序),确保在按当前位排序时,先排序的相同数字保持相对位置。
function getMax(arr) {
    return Math.max(...arr); // 返回数组中的最大值
}

function countingSortForRadix(arr, place) {
    const output = new Array(arr.length); // 输出数组
    const count = new Array(10).fill(0); // 计数数组(假设使用0-9的数字)

    // 计算每个数字在当前位的出现次数
    for (let i = 0; i < arr.length; i++) {
        const digit = Math.floor((arr[i] / place) % 10);
        count[digit]++;
    }

    // 修改count数组,使它包含每个数字的位置
    for (let i = 1; i < count.length; i++) {
        count[i] += count[i - 1];
    }

    // 构建输出数组
    for (let i = arr.length - 1; i >= 0; i--) {
        const digit = Math.floor((arr[i] / place) % 10);
        output[count[digit] - 1] = arr[i];
        count[digit]--;
    }

    // 将已排序的数组填回原数组
    for (let i = 0; i < arr.length; i++) {
        arr[i] = output[i];
    }
}

function radixSort(arr) {
    // 1. 找到最大值
    const max = getMax(arr);

    // 2. 对每一位进行排序
    for (let place = 1; Math.floor(max / place) > 0; place *= 10) {
        countingSortForRadix(arr, place);
    }

    return arr;
}

// 使用示例
const unsortedArray = [170, 45, 75, 90, 802, 24, 2, 66];
const sortedArray = radixSort(unsortedArray);
console.log(sortedArray); // 输出: [2, 24, 45, 66, 75, 90, 170, 802]

思路解释:

  1. 找出最大值:使用getMax函数找到输入数组中的最大数字,以确定需要排序的位数。

  2. 逐位排序:在radixSort函数中,使用循环对每一位进行排序。从最低位开始,每次乘以10,直到处理完所有的位。

  3. 计数排序countingSortForRadix函数作为辅助函数,执行基于特定位的计数排序。主要分为几个步骤:

    • 先计算每个数字在当前位的出现次数,存储在count数组中。
    • 修改count数组,计算每个数字的最终位置。
    • 从后向前填充output数组,以确保排序的稳定性(相同元素的相对位置保持不变)。
    • 最后,将output数组的内容复制回原数组中。

基数排序适用于处理整数和字符串,尤其是在位数不多的情况下性能优越。其时间复杂度为O(d * (n + k)),其中d是数字的位数,n是待排序的元素数量,k是数字范围。