0

0

JavaScript归并排序实现中的常见错误与优化实践

聖光之護

聖光之護

发布时间:2025-11-11 13:28:46

|

251人浏览过

|

来源于php中文网

原创

JavaScript归并排序实现中的常见错误与优化实践

本文深入剖析了javascript归并排序(merge sort)实现中常见的索引处理、数组复制及边界条件错误,并提供了详细的修正方案和优化建议。通过对比错误代码与优化后的实现,重点阐述了如何采用“左闭右开”区间约定、高效的位运算以及精简的合并逻辑,以构建一个健壮、高效且符合javascript编程习惯的归并排序算法

归并排序概述

归并排序是一种高效、稳定的排序算法,它采用分治法(Divide and Conquer)策略。其基本思想是将一个大数组递归地分解为两个子数组,直到子数组只包含一个元素(或为空),然后将这些子数组两两合并,每次合并都确保子数组有序,最终得到一个完全有序的数组。

归并排序主要包含两个核心函数:

  1. mergesort(arr, left, right): 负责递归地将数组分解。
  2. merge(arr, left, mid, right): 负责将两个已排序的子数组合并成一个更大的有序数组。

常见实现错误分析

在实现归并排序时,开发者常因索引处理不当、边界条件模糊或效率考量不足而引入错误。以下是对一段典型错误代码的详细分析:

function mergesort(arr, left, right) {
    if (left < right) {
        let mid = parseInt((right - left) / 2) + left; // 问题1:效率较低的中间值计算
        mergesort(arr, left, mid);
        mergesort(arr, mid + 1, right); // 问题2:与后续merge函数的区间定义可能不一致
        merge(arr, left, mid, right);
    }
}

function merge(arr, left, mid, right) {
    let i = left, j = mid + 1, k = 0, temp = [];
    // 合并两个子数组到temp
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) {
            temp[k] = arr[i];
            i++;
            k++;
        } else {
            temp[k] = arr[j];
            j++;
            k++;
        }
    }
    // 复制剩余元素(如果存在)
    for (; i <= mid; i++) { // 问题3:此循环在特定情况下是多余的
        temp[k] = arr[i];
        k++;
    }
    for (; j <= right; j++) { // 问题3:此循环在特定情况下是多余的
        temp[k] = arr[j];
        k++;
    }
    // 将temp数组复制回原数组arr
    for (let i = left; i <= right; i++) { // 核心问题:索引错误
        arr[i] = temp[i]; // 问题4:temp数组的索引k是从0开始,而arr的索引i是从left开始
    }
}

let arr = [ 5, 3, 7, 2, 9, 12, 4 ];
let n = arr.length;
mergesort(arr, 0, n); // 问题5:初始调用时right参数的语义不一致
console.log(arr); // 输出: [undefined, undefined, undefined, undefined, undefined, undefined, 3, 5]

核心错误解析

  1. merge 函数中的数组回写错误 (问题4): 这是导致输出结果出现 undefined 的主要原因。在 merge 函数的最后一个循环中,目的是将 temp 数组中的有序元素复制回 arr 数组的 [left, right] 区间。temp 数组的元素是从索引 0 开始填充的,而 arr 数组的目标区间是从 left 开始的。 错误的写法 arr[i] = temp[i] 导致:

    • 当 i 从 left 开始时,它尝试访问 temp[left]。如果 left 不为 0,temp[left] 可能越界(temp 的实际长度是 k),或者访问到 temp 中未填充的 undefined 值。
    • 正确的回写逻辑应该是将 temp 中从 0 到 k-1 的元素,依次放入 arr 中从 left 到 right 的位置。

    修正方案

    立即学习Java免费学习笔记(深入)”;

    ImgCleaner
    ImgCleaner

    一键去除图片内的任意文字,人物和对象

    下载
    for (let idx = 0; idx < k; idx++) {
        arr[left + idx] = temp[idx];
    }

    这里,idx 用于遍历 temp 数组,而 left + idx 则对应 arr 数组中正确的起始位置。

  2. mergesort 初始调用时的 right 参数语义不一致 (问题5): 在原始代码中,mergesort(arr, left, right) 函数内部的 if (left 最后一个元素的索引(即 [left, right] 闭区间)。 然而,初始调用 mergesort(arr, 0, n) 中,n 是数组的长度 arr.length,这通常表示区间的结束位置(不包含),即 [0, n) 左闭右开区间。这种不一致导致当 arr.length 传递给 right 时,实际处理的数组范围可能超出预期,或者在某些边界条件下出错。

    最佳实践:采用统一的“左闭右开”区间约定 [left, right),其中 right 表示区间的结束位置(不包含)。这在许多编程语言标准库中是常见的做法,可以简化边界条件的处理。

其他优化建议

  1. 中间值 mid 的计算 (问题1): 原始代码 let mid = parseInt((right - left) / 2) + left; 包含了字符串转换和解析,效率较低。更高效且推荐的做法是使用位运算: let mid = left + ((right - left) >> 1);>> 1 等同于向下取整的除以2,且性能更优。

  2. merge 函数中的冗余循环 (问题3): 在 merge 函数中,当一个子数组的元素全部被复制到 temp 后,另一个子数组中剩余的元素可以直接复制过去。 for (; i

采用“左闭右开”区间的优化实现

考虑到上述问题和优化点,以下是采用“左闭右开”区间 [left, right) 约定实现的归并排序:

/**
 * 归并排序函数
 * @param {Array} arr 待排序数组
 * @param {number} left 区间起始索引(包含)
 * @param {number} right 区间结束索引(不包含)
 */
function mergesort(arr, left, right) {
    // 当区间长度大于1时才需要排序
    if (right - left > 1) {
        // 计算中间索引,采用位运算,等同于 (left + right) / 2 并向下取整
        // 避免 (left + right) 溢出,同时保证在 left 和 right 之间
        let mid = left + ((right - left) >> 1);

        // 递归排序左半部分 [left, mid)
        mergesort(arr, left, mid);
        // 递归排序右半部分 [mid, right)
        mergesort(arr, mid, right);

        // 合并两个有序子数组
        merge(arr, left, mid, right);
    }
}

/**
 * 合并两个有序子数组
 * @param {Array} arr 原始数组
 * @param {number} left 第一个子数组的起始索引(包含)
 * @param {number} mid 第一个子数组的结束索引(不包含),也是第二个子数组的起始索引(包含)
 * @param {number} right 第二个子数组的结束索引(不包含)
 */
function merge(arr, left, mid, right) {
    let i = left;  // 左子数组的当前索引 [left, mid)
    let j = mid;   // 右子数组的当前索引 [mid, right)
    let k = 0;     // 临时数组的当前索引
    let temp = []; // 临时存储合并结果的数组

    // 比较两个子数组的元素,将较小的放入temp
    while (i < mid && j < right) {
        if (arr[i] <= arr[j]) {
            temp[k++] = arr[i++]; // 放入temp并递增索引
        } else {
            temp[k++] = arr[j++]; // 放入temp并递增索引
        }
    }

    // 将左子数组中剩余的元素复制到temp (如果存在)
    while (i < mid) {
        temp[k++] = arr[i++];
    }

    // 将右子数组中剩余的元素复制到temp (如果存在)
    // 注意:如果左子数组已处理完,这部分才可能执行
    // 实际上,这两个while循环只有一个会真正执行,因为另一个子数组已经处理完毕
    // 或者两者都执行,直到其中一个子数组耗尽
    // 采用 [left, right) 约定,此处的 j < right 是正确的
    // 并且不再需要额外的循环来处理剩余部分,因为上面的while循环已经处理了所有情况
    // 实际上,第二个 while 循环 (j < right) 在这种写法下是多余的,因为如果 i < mid 结束,j < right 必然成立,且 j 已经移动到正确位置
    // 但为了清晰,保留一个处理 j 的循环,虽然在实际运行时,如果 i 已经走完,j 对应的元素会直接被复制。
    while (j < right) { // 修正:此循环是必要的,确保右侧剩余元素也被复制
        temp[k++] = arr[j++];
    }

    // 将temp数组中的有序元素复制回原数组arr的对应区间
    for (let idx = 0; idx < k; idx++) {
        arr[left + idx] = temp[idx];
    }
}

// 示例用法
let arr = [ 5, 3, 7, 2, 9, 12, 4 ];
mergesort(arr, 0, arr.length); // 初始调用,right参数为数组长度,符合左闭右开约定
console.log(arr); // 输出: [2, 3, 4, 5, 7, 9, 12]

对 merge 函数中剩余元素处理的进一步说明: 在上述优化后的 merge 函数中,while (i

总结与注意事项

  1. 统一索引约定:在实现分治算法时,选择并严格遵循一种索引约定(例如“左闭右开” [left, right) 或“左闭右闭” [left, right])至关重要。这有助于避免边界条件错误,并使代码更易读、更健壮。
  2. 精确的索引计算:尤其是在将临时数组的内容回写到原数组时,必须确保索引的正确偏移。arr[left + idx] = temp[idx] 这种模式是处理临时数组回写到原数组子区间的标准做法。
  3. 优化性能:使用位运算 >> 1 代替 parseInt((...)/2) 进行整数除法,可以提高代码执行效率。
  4. 避免冗余操作:仔细检查循环和条件语句,消除不必要的计算或重复的代码块,可以使算法更简洁高效。
  5. 内存开销:归并排序通常需要额外的 O(n) 空间来存储临时数组。在内存受限的环境下,可能需要考虑原地归并排序或其他排序算法。

通过理解和应用这些原则,开发者可以编写出高效、正确且易于维护的归并排序实现。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

776

2023.08.22

sort排序函数用法
sort排序函数用法

sort排序函数的用法:1、对列表进行排序,默认情况下,sort函数按升序排序,因此最终输出的结果是按从小到大的顺序排列的;2、对元组进行排序,默认情况下,sort函数按元素的大小进行排序,因此最终输出的结果是按从小到大的顺序排列的;3、对字典进行排序,由于字典是无序的,因此排序后的结果仍然是原来的字典,使用一个lambda表达式作为key参数的值,用于指定排序的依据。

391

2023.09.04

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

94

2023.09.25

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1500

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

624

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

613

2024.03.22

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

158

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 4.2万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号