在 JavaScript 中构建 Matrix 数据结构:30 seconds of code 完整实现指南 教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载矩阵Matrix是动态规划、图形图像处理、机器学习等领域中绕不开的二维数据结构而 JavaScript 原生的 2D 数组在初始化、维度获取、边界校验与数学运算上并不顺手。本文将基于 30 seconds of code 仓库中的 matrix-data-structure.md 文档从零构建一个功能完备的Matrix类涵盖初始化、迭代、取值、数学运算、矩阵运算、谓词匹配、变换与序列化等全套能力。读完本文你将获得一套可直接复制使用、也可按需扩展的矩阵操作工具箱并理解其底层设计取舍。[!NOTE]原文档明确说明该实现并非完备而是一个覆盖面较广的基础解决方案。欢迎按你的需求自由扩展。数据结构为什么选择 2D 数组在对比了 1D 与 2D 数组的各种实现后作者发现朴素的 2D 数组方案在运算性能上最为高效。因此Matrix类在data二维数组之上额外维护了rows行数与cols列数两个属性const matrix new Matrix([ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]); // Matrix { // rows: 3, cols: 3, // data: [ // [ 1, 2, 3 ], // [ 4, 5, 6 ], // [ 7, 8, 9 ] // ] // }缓存行列数的意义在于避免每次访问矩阵维度时重复计算data.length与data[0].length尤其在高频迭代与数学运算中能省去不必要的开销。这是整个类设计的基石后续所有方法都围绕rows、cols与data三个字段展开。初始化构造、填充与工厂方法构造器支持两种入参形态直接传入二维数组或传入{ rows, cols }尺寸描述对象。传入尺寸对象时会自动用0填充。在初始化阶段实例方法与静态工厂方法分工明确fill用指定值填充整个矩阵。copy对矩阵做深拷贝逐行逐值复制避免共享引用。from根据{ rows, cols }创建矩阵并用0填充。zeroes与from语义相同零矩阵。identity创建指定大小的单位矩阵——对角线为1、其余位置为0的方阵。class Matrix { constructor(data) { if (Array.isArray(data)) { this.rows data.length; this.cols data[0].length; this.data data; } else { this.rows data.rows; this.cols data.cols; this.fill(0); } } static from({ rows, cols }) { return new Matrix({ rows, cols }); } static zeroes({ rows, cols }) { return new Matrix({ rows, cols }); } static identity({ size }) { return new Matrix( Array.from({ length: size }, (_, i) Array.from({ length: size }, (_, j) (i j ? 1 : 0)) ) ); } fill(value) { this.data Array.from({ length: this.rows }, () Array.from({ length: this.cols }, () value) ); } copy() { return new Matrix(this.data.map(row row.map(value value))); } }值得留意的是fill的实现它通过Array.from为每个行数组重新分配独立数组避免所有行共享同一数组引用——这是 2D 数组初始化的经典陷阱仓库中 initialize-2d-array.md 一文专门讨论了这一系列初始化技巧含按映射函数生成坐标网格、以及行宽不等的倾斜数组场景与本节互为补充。identity则利用i j判定对角线位置是矩阵算法中最常用的基础操作之一。迭代借鉴 Map 的迭代器设计为了让矩阵遍历尽可能无痛作者借鉴了原生Map数据结构的迭代器设计通过生成器函数提供四种迭代入口indexes逐行逐列产出索引对[i, j]。values产出矩阵中的每一个值。entries同时产出索引与值[i, j, value]。Symbol.iterator让矩阵本身可被for...of、展开运算符直接遍历产出所有值。class Matrix { *indexes() { for (let i 0; i this.rows; i) for (let j 0; j this.cols; j) yield [i, j]; } *values() { yield* this[Symbol.iterator](); } *entries() { for (let [i, j] of this.indexes()) yield [i, j, this.data[i][j]]; } *[Symbol.iterator]() { for (let [i, j] of this.indexes()) yield this.data[i][j]; } }其中Symbol.iterator是整个类可用性的关键——它让for (let value of matrix)这样的原生语法直接生效也是后续reduce、includes、flatMap等大量方法能够复用迭代逻辑的前提。关于Symbol.iterator与生成器如何让任意值可迭代的原理可参考仓库中的 make-iterable.md 片段包括为对象、数字类等自定义迭代器的示例。访问与修改get / set / row / col 与边界校验为了安全地读写矩阵数据类提供四个访问方法并配套一个边界校验方法get(i, j)返回(i, j)处的值。set(i, j, value)设置(i, j)处的值。row(i)返回第i行的数组。col(j)返回第j列的值组成的数组。所有方法在操作前都会调用checkIndex进行越界检查越界时抛出RangeError而不是返回undefined或静默出错class Matrix { checkIndex(i, j) { if (i 0 || i this.rows || j 0 || j this.cols) throw new RangeError(Index out of bounds); } get(i, j) { this.checkIndex(i, j); return this.data[i][j]; } set(i, j, value) { this.checkIndex(i, j); this.data[i][j] value; } row(i) { this.checkIndex(i, 0); return this.data[i]; } col(j) { this.checkIndex(0, j); return this.data.map(row row[j]); } }注意col的实现依赖Array.prototype.map逐行取第j个元素时间复杂度为 O(rows)而row直接返回底层行数组的引用非拷贝这与copy方法的语义形成对照使用时需留意是否会产生意外的共享引用修改。基本数学运算加、减、乘与标量乘法矩阵最常见的用途是数学运算基本四则运算构成更复杂运算的地基。以下四个方法分别实现矩阵加法、减法、矩阵乘法与标量乘法add/subtract要求两个矩阵行列完全一致逐元素相加/相减。multiply矩阵乘法要求左矩阵列数等于右矩阵行数this.cols matrix.rows结果矩阵尺寸为this.rows × matrix.cols。multiplyWithScalar将矩阵每个元素乘以标量。class Matrix { add(matrix) { if (this.rows ! matrix.rows || this.cols ! matrix.cols) throw new Error(Matrix dimensions do not match); return new Matrix( this.data.map((row, i) row.map((value, j) value matrix.data[i][j]) ) ); } subtract(matrix) { if (this.rows ! matrix.rows || this.cols ! matrix.cols) throw new Error(Matrix dimensions do not match); return new Matrix( this.data.map((row, i) row.map((value, j) value - matrix.data[i][j]) ) ); } multiply(matrix) { if (this.cols ! matrix.rows) throw new Error(Matrix dimensions do not match); const result Array.from({ length: this.rows }, () []); for (let i 0; i this.rows; i) { for (let j 0; j matrix.cols; j) { result[i][j] 0; for (let k 0; k this.cols; k) { result[i][j] this.data[i][k] * matrix.data[k][j]; } } } return new Matrix(result); } multiplyWithScalar(scalar) { return new Matrix(this.data.map(row row.map(value value * scalar))); } }[!TIP]矩阵乘法三重循环对初学者略有难度结果[i][j]是左矩阵第i行与右矩阵第j列的对应元素乘积之和。掌握后会发现它的规则非常规整。所有运算方法都遵循返回新矩阵、不改动原矩阵的不可变风格并通过显式抛错来约束维度匹配这与后续各类方法的设计保持一致。附加数学运算极值、统计量与累计运算基础四则运算之外数值分析类的常用操作也被成体系地纳入。原文档一次性添加了极值、求和、乘积、均值、方差、标准差与累计运算且几乎每种都提供全局 / 按行 / 按列三种变体max/maxIndex/maxPerRow/maxPerCol最大值、最大值索引、每行最大值、每列最大值。min/minIndex/minPerRow/minPerCol最小值系列。sum/sumPerRow/sumPerCol求和系列。prod/prodPerRow/prodPerCol求积系列。mean/meanPerRow/meanPerCol均值系列。variance/variancePerRow/variancePerCol方差系列。std/stdPerRow/stdPerCol标准差系列方差的平方根。cumulativeSum/cumulativeSumPerRow/cumulativeSumPerCol累计和系列。cumulativeProd/cumulativeProdPerRow/cumulativeProdPerCol累计积系列。class Matrix { max() { return this.reduce((acc, value) Math.max(acc, value), this.data[0][0]); } maxPerRow() { return this.data.map(row Math.max(...row)); } maxPerCol() { const result Array.from({ length: this.cols }, (_, j) this.data[0][j]); for (let [, j, value] of this.entries()) if (value result[j]) result[j] value; return result; } maxIndex() { return this.reduce( ([maxValue, maxIndex], value, [i, j]) { if (value maxValue) { maxValue value; maxIndex [i, j]; } return [maxValue, maxIndex]; }, [this.data[0][0], [0, 0]] )[1]; } min() { return this.reduce((acc, value) Math.min(acc, value), this.data[0][0]); } minPerRow() { return this.data.map(row Math.min(...row)); } minPerCol() { const result Array.from({ length: this.cols }, (_, j) this.data[0][j]); for (let [, j, value] of this.entries()) if (value result[j]) result[j] value; return result; } minIndex() { return this.reduce( ([minValue, minIndex], value, [i, j]) { if (value minValue) { minValue value; minIndex [i, j]; } return [minValue, minIndex]; }, [this.data[0][0], [0, 0]] )[1]; } sum() { return this.reduce((acc, value) acc value, 0); } sumPerRow() { return this.data.map(row row.reduce((acc, value) acc value, 0)); } sumPerCol() { const result Array.from({ length: this.cols }, () 0); for (let [, j, value] of this.entries()) result[j] value; return result; } prod() { return this.reduce((acc, value) acc * value, 1); } prodPerRow() { return this.data.map(row row.reduce((acc, value) acc * value, 1)); } prodPerCol() { const result Array.from({ length: this.cols }, () 1); for (let [, j, value] of this.entries()) result[j] * value; return result; } mean() { return this.sum() / (this.rows * this.cols); } meanPerRow() { return this.sumPerRow().map(sum sum / this.cols); } meanPerCol() { return this.sumPerCol().map(sum sum / this.rows); } variance() { const mean this.mean(); return ( this.reduce((acc, value) acc Math.pow(value - mean, 2), 0) / (this.rows * this.cols) ); } variancePerRow() { return this.meanPerRow().map( (mean, i) this.data[i].reduce( (acc, value) acc Math.pow(value - mean, 2), 0 ) / this.cols ); } variancePerCol() { return this.meanPerCol().map((mean, j) { let sum 0; for (let i 0; i this.rows; i) { sum Math.pow(this.data[i][j] - mean, 2); } return sum / this.rows; }); } std() { return Math.sqrt(this.variance()); } stdPerRow() { return this.variancePerRow().map(variance Math.sqrt(variance)); } stdPerCol() { return this.variancePerCol().map(variance Math.sqrt(variance)); } cumulativeSum() { const result Array.from({ length: this.rows }, () []); let lastValue 0; for (let [i, j, value] of this.entries()) { lastValue value; result[i][j] lastValue; } return new Matrix(result); } cumulativeSumPerRow() { const result Array.from({ length: this.rows }, () []); for (let i 0; i this.rows; i) { let lastValue 0; for (let j 0; j this.cols; j) { lastValue this.data[i][j]; result[i][j] lastValue; } } return new Matrix(result); } cumulativeSumPerCol() { const result Array.from({ length: this.rows }, () []); for (let j 0; j this.cols; j) { let lastValue 0; for (let i 0; i this.rows; i) { lastValue this.data[i][j]; result[i][j] lastValue; } } return new Matrix(result); } cumulativeProd() { const result Array.from({ length: this.rows }, () []); let lastValue 1; for (let [i, j, value] of this.entries()) { lastValue * value; result[i][j] lastValue; } return new Matrix(result); } cumulativeProdPerRow() { const result Array.from({ length: this.rows }, () []); for (let i 0; i this.rows; i) { let lastValue 1; for (let j 0; j this.cols; j) { lastValue * this.data[i][j]; result[i][j] lastValue; } } return new Matrix(result); } cumulativeProdPerCol() { const result Array.from({ length: this.rows }, () []); for (let j 0; j this.cols; j) { let lastValue 1; for (let i 0; i this.rows; i) { lastValue * this.data[i][j]; result[i][j] lastValue; } } return new Matrix(result); } }从实现细节可以看出几个设计亮点全局统计大多复用reduce与迭代器如max()以data[0][0]为初始值maxIndex()在累加器中同时追踪最大值与坐标按列统计则利用entries()逐元素扫描并以列索引j累加避免为每列单独分配中间数组mean系列基于已实现的sum系列直接换算std系列则是variance系列开平方。关于一维数值数组上的对应运算求和、均值、乘积、加权平均、标准差等可参考 numeric-array-math-operations.md矩阵版的统计方法正是这些思路向二维的推广。矩阵运算转置、对角线、迹与行列式矩阵特有的代数运算比逐元素操作更复杂原文档实现了作者目前所能掌握的基础部分。转置Transpose转置生成一个新矩阵其行是原矩阵的列常用于线性代数、机器学习与数据分析。实现上直接交换行列下标即可class Matrix { transpose() { const result Array.from({ length: this.cols }, () []); for (let i 0; i this.cols; i) for (let j 0; j this.rows; j) result[i][j] this.data[j][i]; return new Matrix(result); } }对纯数组版本的转置基于Array.prototype.map的一行实现可参考 transpose-matrix.md其中还演示了非方阵的转置场景。对角线Diagonal与迹Trace对角线是取出矩阵主对角线i j元素组成的一维向量迹则是对角线元素之和要求矩阵为方阵。二者在编程中应用广泛class Matrix { diagonal() { const result []; const size Math.min(this.rows, this.cols); for (let i 0; i size; i) result[i] this.data[i][i]; return result; } trace() { if (this.rows ! this.cols) throw new Error(Matrix must be square to calculate trace); return this.diagonal().reduce((acc, value) acc value, 0); } }注意diagonal对非方阵也安全取min(rows, cols)为遍历上界而trace严格要求方阵体现了不同运算对前置条件的差异化约束。行列式Determinant与子矩阵行列式是方阵的一个标量属性是线性代数的核心概念。实现需要借助递归与余子式minor概念——删去指定行与列后剩下的矩阵。相关方法minorSubmatrix计算删除指定row与col后的余子式矩阵。submatrix按行列范围(rowStart, colStart, rowEnd, colEnd)截取子矩阵非行列式必需但作为通用辅助方法很有价值。determinant递归计算行列式1×1、2×2 为基线条件高阶按首行展开。class Matrix { minorSubmatrix(row, col) { const result []; for (let i 0; i this.rows; i) { if (i row) continue; const newRow []; for (let j 0; j this.cols; j) { if (j col) continue; newRow.push(this.data[i][j]); } result.push(newRow); } return new Matrix(result); } submatrix(rowStart, colStart, rowEnd, colEnd) { const result []; for (let i rowStart; i rowEnd; i) { const newRow []; for (let j colStart; j colEnd; j) newRow.push(this.data[i][j]); result.push(newRow); } return new Matrix(result); } determinant() { if (this.rows ! this.cols) throw new Error(Matrix must be square to calculate determinant); if (this.rows 1) return this.data[0][0]; if (this.rows 2) return ( this.data[0][0] * this.data[1][1] - this.data[0][1] * this.data[1][0] ); let det 0; for (let j 0; j this.cols; j) { const minor this.minorSubmatrix(0, j); det (j % 2 0 ? 1 : -1) * this.data[0][j] * minor.determinant(); } return det; } }determinant的递归结构清晰可辨1×1 矩阵直接返回自身2×2 矩阵套用ad - bc公式更高阶则按第一行展开通过j % 2 0 ? 1 : -1实现代数余子式的符号交替。关于递归思想的背景知识可参考 recursion.yaml 集合中的相关文章。谓词匹配向数组方法看齐原生数组提供了find、some、every等强大的值匹配方法矩阵类同样补齐了这一能力且回调签名统一为(value, [i, j], matrix)携带值、坐标与矩阵引用every/some所有或部分值满足谓词。find/findIndex/findLast/findLastIndex查找第一个/最后一个满足谓词的值或坐标。includes判断矩阵是否包含某值。indexOf/lastIndexOf查找某值首次/末次出现的坐标。class Matrix { every(callback) { for (let [i, j, value] of this.entries()) if (!callback(value, [i, j], this)) return false; return true; } some(callback) { for (let [i, j, value] of this.entries()) if (callback(value, [i, j], this)) return true; return false; } find(callback) { for (let [i, j, value] of this.entries()) if (callback(value, [i, j], this)) return value; return undefined; } findIndex(callback) { for (let [i, j, value] of this.entries()) if (callback(value, [i, j], this)) return [i, j]; return undefined; } findLast(callback) { for (let i this.rows - 1; i 0; i--) for (let j this.cols - 1; j 0; j--) if (callback(this.data[i][j], [i, j], this)) return this.data[i][j]; return undefined; } findLastIndex(callback) { for (let i this.rows - 1; i 0; i--) for (let j this.cols - 1; j 0; j--) if (callback(this.data[i][j], [i, j], this)) return [i, j]; return undefined; } includes(value) { for (let val of this) if (val value) return true; return false; } indexOf(value) { for (let [i, j, val] of this.entries()) if (val value) return [i, j]; return undefined; } lastIndexOf(value) { for (let i this.rows - 1; i 0; i--) for (let j this.cols - 1; j 0; j--) if (this.data[i][j] value) return [i, j]; return undefined; } }与原生数组最大的差异在于索引相关方法的返回值是[i, j]坐标对而非单个数字这由二维结构的语义决定。findLast系列与lastIndexOf通过反向双重循环实现从矩阵右下角向左上角的逆序扫描与reduceRight的方向保持一致。其他数组操作映射、归约与展平ES6 之后原生数组的实用方法在矩阵语境下同样大有可为本类将其系统地移植了过来。映射与归约forEach、map、reduce、reduceRight四件套齐备。map返回新Matrixreduce支持自定义初始值class Matrix { forEach(callback) { for (let [i, j, value] of this.entries()) callback(value, [i, j], this); } map(callback) { const result Array.from({ length: this.rows }, () []); for (let i 0; i this.rows; i) for (let j 0; j this.cols; j) result[i][j] callback(this.data[i][j], [i, j], this); return new Matrix(result); } reduce(callback, initialValue) { let accumulator initialValue; for (let [i, j, value] of this.entries()) accumulator callback(accumulator, value, [i, j], this); return accumulator; } reduceRight(callback, initialValue) { let accumulator initialValue; for (let i this.rows - 1; i 0; i--) for (let j this.cols - 1; j 0; j--) accumulator callback(accumulator, this.data[i][j], [i, j], this); return accumulator; } }map与reduce是全类的元工具——前文众多统计方法max、sum、variance等都建立在reduce之上map则被filter、mask等过滤类方法复用。展平矩阵本质是 2D 数组展平实现极为简洁class Matrix { flat() { return this.data.flat(2); } flatMap(callback) { return this.map(callback).flat(); } }flat返回一维数组flatMap先映射再展平与原生数组语义对应。过滤谓词过滤与掩码矩阵过滤有多种形态原文档一次性提供了五种且全部返回新矩阵/新数组不做原地修改mask用掩码矩阵2D 数组或Matrix实例或函数过滤不满足处替换为0。filter用谓词函数过滤不满足处替换为空洞undefined。filterNonZero将0值替换为空洞undefined。findMatches用谓词过滤并返回匹配值组成的一维向量。findIndexOfMatches用谓词过滤并返回匹配坐标组成的一维向量。class Matrix { mask(maskValue) { if (Array.isArray(maskValue)) { if (this.rows ! maskValue.length || this.cols ! maskValue[0].length) throw new Error(Matrix dimensions do not match); } else if (maskValue instanceof Matrix) { if (this.rows ! maskValue.rows || this.cols ! maskValue.cols) throw new Error(Matrix dimensions do not match); } else if (typeof maskValue ! function) throw new TypeError(Mask value must be a function or a matrix); const getMaskAt typeof maskValue function ? maskValue : Array.isArray(maskValue) ? (_, [i, j]) maskValue[i][j] : (_, [i, j]) maskValue.data[i][j]; return this.map((value, [i, j]) getMaskAt(value, [i, j], this) ? value : 0 ); } filter(callback) { return this.map((value, [i, j]) callback(value, [i, j], this) ? value : undefined ); } filterNonZero() { return this.map(value (value ! 0 ? value : undefined)); } findMatches(callback) { return this.reduce((acc, value, [i, j]) { if (callback(value, [i, j], this)) acc.push(value); return acc; }, []); } findIndexOfMatches(callback) { return this.reduce((acc, value, [i, j]) { if (callback(value, [i, j], this)) acc.push([i, j]); return acc; }, []); } }mask是其中最灵活的方法三种入参函数、2D 数组、Matrix统一收敛为getMaskAt访问器再通过map应用filter系列使用undefined作为空洞标记这与原生Array.prototype.filter的稀疏数组语义一脉相承。需要特别强调的是这些方法均返回新对象原矩阵不受影响。矩阵变换翻转、旋转、合并与扩展处理图像、图形等场景时矩阵变换是高频需求原文档实现了四类变换。翻转水平/垂直翻转即围绕 y/x 轴旋转矩阵借助 ES2023 的Array.prototype.toReversed()实现得非常干净class Matrix { flipHorizontal() { const result this.data.map(row row.toReversed()); return new Matrix(result); } flipVertical() { const result this.data.toReversed().map(row [...row]); return new Matrix(result); } }注意flipVertical在toReversed()后对每行做了[...row]拷贝避免新矩阵与原矩阵共享行数组引用。旋转顺时针与逆时针 90° 旋转通过坐标映射实现不需要更多角度——多次旋转即可组合出任意结果class Matrix { rotateClockwise() { const result Array.from({ length: this.cols }, () []); for (let i 0; i this.rows; i) for (let j 0; j this.cols; j) result[j][this.rows - i - 1] this.data[i][j]; return new Matrix(result); } rotateCounterClockwise() { const result Array.from({ length: this.cols }, () []); for (let i 0; i this.rows; i) for (let j 0; j this.cols; j) result[this.cols - j - 1][i] this.data[i][j]; return new Matrix(result); } }顺时针旋转的映射规律是原(i, j)元素落在新矩阵的(j, rows - i - 1)位置逆时针则是(cols - j - 1, i)。两者的新矩阵行数都是this.cols。合并合并用于将两个矩阵拼接在一起支持纵向追加行与横向追加列两种方向class Matrix { mergeCols(matrix) { if (this.cols ! matrix.cols) throw new Error(Matrix dimensions do not match); return new Matrix(this.data.concat(matrix.data)); } mergeRows(matrix) { if (this.rows ! matrix.rows) throw new Error(Matrix dimensions do not match); return new Matrix(this.data.map((row, i) row.concat(matrix.data[i]))); } }命名上需要注意mergeCols的约束是列数一致即纵向堆叠行数相加mergeRows的约束是行数一致即横向拼接列数相加。维度不匹配时均抛出Error。扩展扩展即用0填充的新矩阵与原矩阵合并实现上直接复用了mergeCols/mergeRowsclass Matrix { expandRows(rows, fillValue 0) { const newRows new Matrix({ rows, cols: this.cols }); newRows.fill(fillValue); return this.mergeCols(newRows); } expandCols(cols, fillValue 0) { const newCols new Matrix({ rows: this.rows, cols }); newCols.fill(fillValue); return this.mergeRows(newCols); } }fillValue默认0也可传入任意自定义填充值。expandRows追加rows行expandCols追加cols列组合使用即可实现任意方向的尺寸扩展。序列化与反序列化最后是工具性方法JSON 与字符串序列化。原文档指出 CSV 序列化也不难扩展class Matrix { toString() { return this.data.toString(); } toLocaleString() { return this.data.toLocaleString(); } toJSON() { return JSON.stringify(this.data); } static fromJSON(json) { return new Matrix(JSON.parse(json)); } }toString/toLocaleString直接委托给底层数组toJSON返回二维数组的 JSON 字符串fromJSON则反向解析 JSON 并重建Matrix。有了toJSONMatrix实例可以无缝融入JSON.stringify的序列化链路。结语完整性与可扩展性至此一个功能全面的Matrix类就构建完成了从底层 2D 数组与维度缓存出发覆盖初始化、迭代、读写、基本与统计数学运算、转置/对角线/迹/行列式等矩阵运算、谓词匹配、数组操作、几何变换与序列化共数十个方法且绝大多数遵循返回新对象、不改原数据、显式抛错的一致约定。这套实现的定位正如原文档所言——覆盖面较广的基础积木而非完备的线性代数库。它的核心价值在于两点其一为动态规划、图形图像等高频矩阵场景提供了统一、可读的操作接口该片段收录于仓库的>赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐用 JavaScript 实现双向链表30-seconds-of-code 双向链表数据结构完整指南用 JavaScript 实现双向链表30 seconds of code 双向链表数据结构完整指南 双向链表Doubly Linked List是链表家教程文档30 seconds of code用 JavaScript 从零实现通用 Graph图数据结构30 seconds of code用 JavaScript 从零实现通用 Graph图数据结构 在 30 seconds of code 项目的 Jav教程文档30 Seconds of Code 中 JavaScript 队列Queue数据结构的实现与实战解析30 Seconds of Code 中 JavaScript 队列Queue数据结构的实现与实战解析 队列是一种遵循先进先出FIFO原则的线性数据结构教程文档上一篇终极Wand-Enhancer使用指南免费解锁WeMod全部高级功能下一篇MediaPipe TouchDesigner插件GPU加速的实时AI视觉解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考