
本文深入剖析了j*ascript归并排序(merge sort)实现中常见的索引处理、数组复制及边界条件错误,并提供了详细的修正方案和优化建议。通过对比错误代码与优化后的实现,重点阐述了如何采用“左闭右开”区间约定、高效的位运算以及精简的合并逻辑,以构建一个健壮、高效且符合j*ascript编程习惯的归并排序算法。
归并排序是一种高效、稳定的排序算法,它采用分治法(Divide and Conquer)策略。其基本思想是将一个大数组递归地分解为两个子数组,直到子数组只包含一个元素(或为空),然后将这些子数组两两合并,每次合并都确保子数组有序,最终得到一个完全有序的数组。
归并排序主要包含两个核心函数:
在实现归并排序时,开发者常因索引处理不当、边界条件模糊或效率考量不足而引入错误。以下是对一段典型错误代
码的详细分析:
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]merge 函数中的数组回写错误 (问题4): 这是导致输出结果出现 undefined 的主要原因。在 merge 函数的最后一个循环中,目的是将 temp 数组中的有序元素复制回 arr 数组的 [left, right] 区间。temp 数组的元素是从索引 0 开始填充的,而 arr 数组的目标区间是从 left 开始的。 错误的写法 arr[i] = temp[i] 导致:
修正方案:
for (let idx = 0; idx < k; idx++) {
arr[left + idx] = temp[idx];
}这里,idx 用于遍历 temp 数组,而 left + idx 则对应 arr 数组中正确的起始位置。
Tanka
具备AI长期记忆的下一代团队协作沟通工具
146
查看详情
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 表示区间的结束位置(不包含)。这在许多编程语言和标准库中是常见的做法,可以简化边界条件的处理。
中间值 mid 的计算 (问题1): 原始代码 let mid = parseInt((right - left) / 2) + left; 包含了字符串转换和解析,效率较低。更高效且推荐的做法是使用位运算: let mid = left + ((right - left) >> 1);>> 1 等同于向下取整的除以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
通过理解和应用这些原则,开发者可以编写出高效、正确且易于维护的归并排序实现。
以上就是J*aScript归并排序实现中的常见错误与优化实践的详细内容,更多请关注其它相关文章!
相关文章:
网易大神怎么保存别人动态的图片_网易大神动态图片保存方法
MinIO大规模对象列表性能瓶颈深度解析与外部元数据管理策略
Golang如何优雅处理error_Golang error处理最佳实践总结
msn官网入口地址手机版 msn官方网站手机最新链接
蛙漫漫画官网在线入口 蛙漫全本漫画免费阅读平台
Lar*el 中按“Has One Of Many”关联模型排序的最佳实践
解决深度学习模型训练初期异常高损失与完美验证准确率问题
天眼查怎么看公司融资情况 天眼查企业融资历史查询步骤【攻略】
Windows7怎么硬盘安装 Windows7提取ISO镜像到非系统盘并运行setup.exe实现硬盘直装【教程】
J*a递归快速排序中静态变量导致数据累积问题的解决方案
AWS EC2实例间SQL Server连接超时:安全组配置与故障排除指南
虫虫漫画精品漫画官网_虫虫漫画精品漫画官网进入精品漫画
高德地图家和公司地址在哪设置 高德地图通勤路线设置方法【超详细】
凉拌黄瓜怎么拌更入味 凉拌黄瓜简单家常做法
基于多条件高效更新SQL表:利用CASE表达式优化业务逻辑
优化Lar*el Docker镜像:Composer与PHP版本控制策略
响应式CSS Grid布局:优化网格项在小屏幕下的堆叠与宽度适配
PHP教程:高效从URL路径中提取倒数第二个片段
PDF文件体积过大处理_PDF压缩技巧详解
迅雷下载到U盘速度很慢怎么办_迅雷U盘下载慢优化方法
qq游戏免费畅玩入口_qq游戏电脑版快速启动
React Router v6 教程:构建认证保护的私有路由与重定向策略
印象笔记如何设提醒任务防漏执行_印象笔记设提醒任务防漏执行【任务提醒】
微信网页版扫码登录入口 微信网页版二维码登录入口
TikTok国际版网页端快速入口 TikTok全球版短视频浏览教程
Python类型检查:优化关联可选属性的Mypy推断策略
mysql备份恢复性能优化_mysql备份恢复性能优化方法
Safari浏览器输入栏卡顿如何解决 Safari搜索建议与缓存清理
J*a实现学校排课程序_面向对象结构化项目示例
抖音怎么赚钱_抖音创作者变现方法与途径指南
妖精漫画网页版登录入口免费_妖精漫画官网主页直接阅读漫画
汽水音乐在线解析 汽水音乐在线解析入口
Yandex浏览器官方网页版入口 Yandex浏览器最新版官网
NVIDIA股价11月重挫12%:下月有望好转 但难回5万亿美元巅峰
mc.js免安装版 mc.js一键畅玩入口
星露谷物语官网入口 星露谷物语游戏官网入口
AO3中文官网链接_AO3网页版稳定镜像站
4399体育竞技小游戏_4399小游戏赛事入口
汽水音乐车机版8.9下载 汽水音乐车机版8.9版本安装入口
从OpenAI API响应中高效提取生成文本
CSS条件样式无法按设备触发怎么排查_media条件语句正确设置解决触发问题
电脑屏幕颜色不舒服怎么办_Windows夜间模式与色彩校准教程【护眼技巧】
J*aScript实现单选按钮与关联输入框的联动禁用教程
在WordPress中通过REST API访问受BasicAuth保护的站点内容
Python异步编程实践:使用Binance API构建实时交易数据流
QQ邮箱官网登录入口 QQ邮箱网页版邮箱快速登录
Lar*el Form Request 中唯一性验证更新操作的正确实践
DLsite中文平台入口 DLsite官网内容在线查看
vivo手机互传视频怎么操作_vivo手机互传视频详细传输方法
文心一言怎样用插件调度API数据_文心一言用插件调度API数据【API调用】