/**
* LeetCode Hot 100 图解 — 二叉树可视化组件
* 所有二叉树页面通过 引入
* 依赖: algo-viz.js (StepController, renderCode, renderArray 等)
*/
/* ========== 解析数组表示法 → 树结构 ========== */
function parseTreeArray(arr) {
if (!arr || !arr.length) return null;
const nodes = arr.map((v, i) =>
(v !== null && v !== undefined) ? {val: v, idx: i, left: null, right: null} : null
);
for (let i = 0; i < nodes.length; i++) {
if (!nodes[i]) continue;
const li = 2*i+1, ri = 2*i+2;
if (li < nodes.length) nodes[i].left = nodes[li];
if (ri < nodes.length) nodes[i].right = nodes[ri];
}
return nodes[0];
}
/* ========== 计算树的深度 ========== */
function treeDepth(root) {
if (!root) return 0;
return 1 + Math.max(treeDepth(root.left), treeDepth(root.right));
}
/* ========== SVG 渲染二叉树 ========== */
function renderTreeSVG(root, options = {}) {
const {
highlights = {}, // { nodeIdx: 'current'|'visited'|'active'|'found'|'matched'|'target'|'swap'|'result'|'processing'|'path'|'ancestor'|'ignored'|'left'|'right' }
depthLabels = {}, // { nodeIdx: number } 显示深度标注
annotations = {}, // { nodeIdx: string } 节点上方文字
width = 720,
nodeR = 20,
levelH = 72,
} = options;
if (!root) return '
空树
';
const depth = treeDepth(root);
const svgH = depth * levelH + 55;
// 递归布局:每个节点居中在其水平范围内
const pos = {};
function layout(node, xMin, xMax, y) {
if (!node) return;
const x = (xMin + xMax) / 2;
pos[node.idx] = {x, y, val: node.val};
layout(node.left, xMin, x, y + levelH);
layout(node.right, x, xMax, y + levelH);
}
layout(root, 40, width - 40, 32 + nodeR);
// 颜色方案
const colorMap = {
'': {fill:'#dbeafe', stroke:'#3b82f6', text:'#1e40af'},
'default': {fill:'#dbeafe', stroke:'#3b82f6', text:'#1e40af'},
'active': {fill:'#c7d2fe', stroke:'#4f46e5', text:'#3730a3', glow:'3b82f6'},
'current': {fill:'#fef3c7', stroke:'#f59e0b', text:'#92400e', glow:'f59e0b'},
'visited': {fill:'#dcfce7', stroke:'#16a34a', text:'#166534'},
'found': {fill:'#fef3c7', stroke:'#f59e0b', text:'#92400e', glow:'f59e0b'},
'matched': {fill:'#bbf7d0', stroke:'#16a34a', text:'#166534', glow:'16a34a'},
'target': {fill:'#fce7f3', stroke:'#ec4899', text:'#9d174d', glow:'ec4899'},
'swap': {fill:'#fef3c7', stroke:'#f59e0b', text:'#92400e', glow:'f59e0b'},
'left': {fill:'#dbeafe', stroke:'#3b82f6', text:'#1e40af'},
'right': {fill:'#ede9fe', stroke:'#8b5cf6', text:'#5b21b6'},
'result': {fill:'#bbf7d0', stroke:'#16a34a', text:'#166534', glow:'16a34a'},
'processing':{fill:'#e0e7ff', stroke:'#6366f1', text:'#4338ca', glow:'3b82f6'},
'path': {fill:'#fef9c3', stroke:'#eab308', text:'#854d0e', glow:'f59e0b'},
'ancestor': {fill:'#fce7f3', stroke:'#ec4899', text:'#9d174d', glow:'ec4899'},
'ignored': {fill:'#f1f5f9', stroke:'#cbd5e1', text:'#94a3b8'},
};
let svg = `';
return `${svg}
`;
}
/* ========== 遍历索引辅助 ========== */
function inorderIndices(root) {
const r = [];
(function dfs(n){ if(!n)return; dfs(n.left); r.push(n.idx); dfs(n.right); })(root);
return r;
}
function preorderIndices(root) {
const r = [];
(function dfs(n){ if(!n)return; r.push(n.idx); dfs(n.left); dfs(n.right); })(root);
return r;
}
function postorderIndices(root) {
const r = [];
(function dfs(n){ if(!n)return; dfs(n.left); dfs(n.right); r.push(n.idx); })(root);
return r;
}
function levelOrderIndices(root) {
if (!root) return [];
const result = [], queue = [root];
while (queue.length) {
const level = [], sz = queue.length;
for (let i = 0; i < sz; i++) {
const nd = queue.shift();
level.push(nd.idx);
if (nd.left) queue.push(nd.left);
if (nd.right) queue.push(nd.right);
}
result.push(level);
}
return result;
}
/* ========== 收集所有节点(BFS序) ========== */
function allNodesBFS(root) {
if (!root) return [];
const r = [], queue = [root];
while (queue.length) {
const nd = queue.shift();
r.push(nd);
if (nd.left) queue.push(nd.left);
if (nd.right) queue.push(nd.right);
}
return r;
}
/* ========== 解析输入字符串 ========== */
function parseInputTree(str) {
try {
const arr = JSON.parse(str.replace(/[()[\]{}]/g, m => m === '[' || m === ']' ? m : ''));
return arr;
} catch(e) {
return null;
}
}
/* ========== 全局导出 ========== */
window.parseTreeArray = parseTreeArray;
window.treeDepth = treeDepth;
window.renderTreeSVG = renderTreeSVG;
window.inorderIndices = inorderIndices;
window.preorderIndices = preorderIndices;
window.postorderIndices = postorderIndices;
window.levelOrderIndices = levelOrderIndices;
window.allNodesBFS = allNodesBFS;
window.parseInputTree = parseInputTree;