☰
JavaScript实现层次分析法工具:判断矩阵、特征向量与一致性检验
2026/10/26 11:45:23 网站建设 项目流程

简介:这是一款基于 JavaScript 实现的层次分析法(AHP)可视化工具,面向需要借助结构化方法辅助个人或团队决策的学习者与开发者,可用于多准则方案比选、权重计算等场景。压缩包共 14 个文件,约 476KB,以 5 个 js 脚本承载核心计算与模块逻辑,配合 2 个 json 配置、2 个 css 样式及 1 个 html 入口页面,另附 license、notice、md 说明等文档,整体轻量、结构清晰,便于本地直接运行与二次开发。目前已有 814 人学习下载。工具将 AHP 的判断矩阵与权重计算过程可视化呈现,读者可借此理解层次分析法的建模思路与计算流程;源码模块划分明确,适合作为前端练手项目或决策类应用的参考实现。需注意当前版本尚无数据持久化能力,页面回退可能导致输入丢失,作者也欢迎提交拉请求共同完善。

1. 层次分析法工具:为什么一个“打分工具”值得用 JavaScript 重写

很多人第一次接触层次分析法(AHP),是在 Excel 里手敲判断矩阵,然后对着特征向量发呆。表面上看,AHP 就是个“打分加权”的活儿,似乎用不着专门写个工具。但真正做过一次完整 AHP 的人都知道,麻烦不在打分,而在一致性检验——你填的矩阵很可能通不过 CR < 0.1,得回头改,改完又可能牵动别的元素,来回折腾。ahp-tool 这个 JavaScript 工具解决的正是这个循环:把判断矩阵的构造、特征向量求解、一致性比率计算和敏感性回看串成一条可复现的流程。它值得搞懂,因为 AHP 的“玄学感”几乎全部来自矩阵特征值求解和一致性修正这两步,而这两步恰恰是可以用代码讲清楚的。

2. 判断矩阵与特征向量:AHP 的数学骨架到底在算什么

AHP 的核心不是“打分”,而是把人对两两比较的主观判断,压缩成一个可计算的权重向量。这个过程依赖一个关键假设:如果决策者的判断完全一致,那么判断矩阵 A 应该满足 a_ij = w_i / w_j,此时 A 是一个秩为 1 的正互反矩阵,最大特征值恰好等于 n,对应的特征向量就是权重。现实中的判断不可能完全一致,所以最大特征值 λ_max 会略大于 n,偏离程度就成了衡量“判断有多不一致”的标尺。

2.1 正互反矩阵的结构约束

一个 n 阶 AHP 判断矩阵,只有 n(n-1)/2 个自由元素,因为对角线恒为 1,且 a_ji = 1/a_ij。这个约束不是数学装饰,它直接决定了工具的数据结构该怎么设计。常见的实现思路是用一个上三角数组存储,读取时按索引映射回二维坐标,写入时同时维护对称位置。这样做的好处是杜绝了“只改了一半”的脏数据——如果让用户直接编辑二维表,很容易出现 a_ij 改了、a_ji 没跟着变的情况,后续求特征向量时结果就会莫名其妙地偏。

// 上三角压缩存储:values 长度 = n*(n-1)/2 function buildMatrix(n, values) { const A = Array.from({ length: n }, () => Array(n).fill(1)); let k = 0; for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { const v = values[k++]; A[i][j] = v; // 上三角存原始判断 A[j][i] = 1 / v; // 下三角自动取倒数,保证正互反 } } return A; }

这段代码的关键在第 8 行:下三角永远由上三角推导,而不是独立存储。参数 n 是准则或方案的数量,values 是用户逐对比较后得到的 n(n-1)/2 个标度值(通常取 1 到 9 及其倒数)。这样构造出来的矩阵天然满足正互反性,后续求特征值时才不会因为数据不自洽而翻车。

2.2 特征向量的数值求解:幂法为什么够用

理论上权重向量是 A 的主特征向量,可以用解析法求特征多项式,但 n 一旦超过 4,手算基本不现实。工程上普遍用幂法迭代:任取一个正初始向量,反复用 A 去乘并归一化,收敛后得到的就是主特征向量。幂法能 work 的前提是矩阵存在唯一的主特征值,而正互反矩阵恰好满足 Perron-Frobenius 条件,主特征值是正实数且严格占优,所以迭代一定收敛。

function powerIteration(A, maxIter = 100, tol = 1e-9) { const n = A.length; let w = Array(n).fill(1 / n); // 均匀初始权重 let lambda = 0; for (let iter = 0; iter < maxIter; iter++) { const next = A.map(row => row.reduce((s, a, j) => s + a * w[j], 0) // A * w ); const sum = next.reduce((s, x) => s + x, 0); const newW = next.map(x => x / sum); // 归一化 lambda = sum; // 归一化因子即 λ_max 的估计 const diff = newW.reduce((s, x, i) => s + Math.abs(x - w[i]), 0); w = newW; if (diff < tol) break; // 收敛判据 } return { weights: w, lambdaMax: lambda }; }

这里有个容易被忽略的细节:归一化因子 sum 在收敛时正好趋近 λ_max,所以不需要额外再算一次特征值。maxIter 设 100 是保守值,实际 n 在 10 以内时通常 20 次以内就收敛;tol 用 1e-9 是权重精度和浮点误差之间的折中。如果迭代不收敛,八成是矩阵里混进了 0 或负数,幂法对非正矩阵没有保证。

2.3 一致性比率 CR:把“感觉不对”量化成数字

算出 λ_max 之后,一致性指标 CI = (λ_max - n) / (n - 1),再除以随机一致性指标 RI,得到 CR = CI / RI。CR < 0.1 是通行阈值。RI 是一张按 n 查表的经验值,n=1 到 9 分别对应 0、0、0.58、0.90、1.12、1.24、1.32、1.41、1.45。这张表不是推导出来的,而是大量随机正互反矩阵的 CI 平均值,所以工具里必须内置这张表,不能现场算。

n123456789
RI000.580.901.121.241.321.411.45

n=1 和 n=2 时 CR 恒为 0,因为二阶正互反矩阵天然完全一致,这一点在工具里要特殊处理,否则会出现除零。很多手写脚本在这里翻车,就是因为没判断 n < 3 的情况。

3. 一致性修正与敏感性:工具真正省时间的地方

算出 CR 超标只是开始,真正耗人的是“改哪个元素”。ahp-tool 这类工具的价值,在于把修正过程从盲试变成有方向的调整。常见做法是定位对一致性破坏最大的元素对,提示用户重新审视那一对比较,而不是让人从头再填一遍。

3.1 定位不一致的来源:偏差矩阵

一个实用的思路是构造偏差矩阵 D,其中 d_ij = a_ij * w_j / w_i。如果判断完全一致,D 的所有元素都等于 1;偏离 1 越远,说明这一对比较与整体权重越矛盾。工具可以按 |d_ij - 1| 排序,把最矛盾的几对高亮出来。

function inconsistencyMap(A, weights) { const n = A.length; const issues = []; for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { const expected = weights[i] / weights[j]; // 一致时应有的比值 const deviation = Math.abs(A[i][j] / expected - 1); issues.push({ i, j, deviation }); } } return issues.sort((a, b) => b.deviation - a.deviation); }

deviation 越大,说明用户填的 a_ij 和最终权重推出来的比值差距越大。参数 weights 来自上一节的幂法结果。这个函数不直接改矩阵,而是给出“最值得复核的比较对”,把修正变成有靶子的动作。我一般会建议用户只调 deviation 排名前三的对,因为一致性是全局性质,局部大改往往比全面微调更快收敛。

3.2 敏感性分析:权重稳不稳,换个标度会不会翻盘

AHP 的另一个痛点是标度主观性。同一组比较,有人用 3 表示“稍重要”,有人用 4,最终权重排序可能变。敏感性分析就是回答“排序对扰动有多敏感”。简单做法是对每个 a_ij 施加 ±10% 的扰动,重算权重,观察排序是否翻转。工具里可以只对排名相邻的方案做这个检查,计算量可控。

function sensitivityCheck(A, baseWeights, delta = 0.1) { const n = A.length; const flips = []; for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { const B = A.map(r => r.slice()); B[i][j] *= (1 + delta); B[j][i] = 1 / B[i][j]; // 保持正互反 const { weights } = powerIteration(B); // 检查原本 i 优于 j 的关系是否反转 if ((baseWeights[i] - baseWeights[j]) * (weights[i] - weights[j]) < 0) { flips.push({ pair: [i, j], delta }); } } } return flips; }

这段代码对每一对元素做单点扰动,delta 默认 10%。flips 为空说明排序稳健;如果某个方案对频繁出现在 flips 里,就说明它的排名是“踩线”的,汇报时应当注明。注意每次扰动后都要重新归一化下三角,否则矩阵不再是正互反,幂法结果没有意义。

3.3 群决策聚合:多个判断矩阵怎么合并

实际决策往往不止一个人填矩阵。聚合方式有两种:先聚合矩阵再求权重(AIJ),或先各自求权重再加权平均(AIP)。AIJ 用几何平均更合理,因为判断值是比率标度,算术平均会破坏倒数关系。

function geometricAggregate(matrices) { const n = matrices[0].length; const G = Array.from({ length: n }, () => Array(n).fill(1)); for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { let prod = 1; for (const M of matrices) prod *= M[i][j]; G[i][j] = Math.pow(prod, 1 / matrices.length); // 几何平均 } } return G; }

几何平均保证聚合后的矩阵仍是正互反的:G[j][i] 自然等于 1/G[i][j]。如果改用算术平均,这个性质就丢了,后续一致性检验会失真。参数 matrices 是各决策者的判断矩阵数组,要求维度一致。

4. 避坑与排查:AHP 工具里最容易翻车的几处

4.1 CR 算出来是 NaN

现象:一致性比率显示 NaN,权重全是 0 或无穷大。原因通常是矩阵里出现了 0 或负数,幂法归一化时除零。解决:在 buildMatrix 入口校验每个标度值必须大于 0,且落在 1/9 到 9 之间,越界直接拒绝而不是静默修正。

4.2 二阶矩阵报除零

现象:n=2 时 CR 计算抛异常。原因:CI 公式分母 n-1 为 1 没问题,但 RI 表中 n=2 对应 0,CR = CI/0。解决:n < 3 时直接返回 CR=0,跳过除法,并在界面上说明二阶无需一致性检验。

4.3 改了上三角,下三角没同步

现象:用户手动编辑后,a_ij 和 a_ji 不再互为倒数,权重结果诡异。原因:数据结构允许独立写入下三角。解决:所有写入走统一入口,下三角永远由 1/a_ij 推导,禁止直接赋值。这是血泪经验,早期版本就栽在这。

4.4 幂法不收敛但没报错

现象:迭代跑满 maxIter,权重看起来“差不多”,但 CR 明显异常。原因:收敛判据太松或矩阵接近不可约边界。解决:迭代结束后检查 diff 是否真的小于 tol,不满足就抛出警告,提示用户检查矩阵是否存在极端值(如 1/9 和 9 混用过多)。

4.5 群决策聚合后一致性反而变差

现象:每个人单独 CR 都合格,几何平均后 CR 超标。原因:不同决策者的判断方向冲突,聚合放大了矛盾。解决:聚合前先看各人权重向量的排序是否一致,排序分歧大时不应强行聚合,而应回到讨论环节。工具可以输出各人排序对比表作为提示。

5. 设计取舍:为什么用 JavaScript 做 AHP,以及边界在哪

把 AHP 做成 JavaScript 工具,最直接的动机是“在浏览器里跑完整个流程”。判断矩阵的规模通常很小(n 很少超过 10),幂法迭代的计算量对现代 JS 引擎来说微不足道,所以性能不是瓶颈,交互和可复现才是。用 JS 意味着矩阵构造、实时 CR 反馈、敏感性扫描都能在客户端完成,不需要把决策数据传到服务端,这对涉及内部评审的场景是个实际优势。

但取舍也很明显。JS 的浮点数精度在 n 较大、标度极端时会让幂法收敛变慢,甚至出现权重微小负值(理论上不可能,数值误差导致)。常见的处理思路是在归一化后做一次截断,把小于 1e-12 的分量置零再重新归一化,而不是放任负值进入后续计算。另一个边界是 RI 表只覆盖到 n=9,超过 9 个元素时 AHP 本身的一致性判断就不可靠了,工具应当明确拒绝而不是硬算。

验证一个 AHP 实现是否可信,我习惯用两个固定用例:完全一致的矩阵(如所有权重按 2:1:1 构造)应当得到 CR≈0 且权重精确;随机矩阵应当得到 CR 明显大于 0.1。这两个用例跑通,基本能排除大部分实现错误。从那以后我每次接入新的 AHP 库,都强制先跑这两组对照,再谈业务逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询