前言
最近在刷一些二叉树的题,发现调试起来实在是麻烦又不够直观,要是可以随机生成一颗二叉树并渲染,解完题后实时查看树的变化就方便多了。
发现有篇文章实现的挺好:juejin.cn/post/696209…
生成二叉树
class TreeNode {
constructor(val) {
this.val = val
this.left = null
this.right = null
}
}
class FullBinaryTree {
constructor(level) {
const _Root = this.getFullBinaryTree(level)
this.getRoot = () => {
return _Root
}
}
getFullBinaryTree(level) {
debugger
let index = 0;
function fullTree(n) {
if (n === 0) return null;
let root = new TreeNode(index++);
root.left = fullTree((n - 1) / 2);
root.right = fullTree((n - 1) / 2);
return root;
}
// 2的level次方 -1 = 总节点数
return fullTree(Math.pow(2, level) - 1);
}
}
const Root = new FullBinaryTree(3).getRoot() // 获取一个深度为3的树的根结点
基于这颗树可以再写一些工具方法
计算深度
const getTreeDepth = (root) => {
let depth = 0 // 当前深度
let max = 0 // 当前最大深度
const solve = (node) => {
if (!node) {
if (depth >= max) {
max = depth
}
return 0
}
depth++
solve(node.left)
solve(node.right)
depth-- // 回到当前节点,深度减回来
return max
}
return solve(root)
}
渲染器(Render)
用canvas作为渲染器
initSize : 初始化canvas
renderNode: 绘制圆形节点,填充节点值
renderLine: 连接两个节点
class CanvasRender {
constructor(container) {
this.canvas = document.createElement('canvas');
this.container = container;
this.ctx = this.canvas.getContext('2d');
}
initSize(w, h) {
this.canvas.width = w;
this.canvas.height = h;
this.container.appendChild(this.canvas)
}
setStyle(ctx) {
ctx.font = "20px serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
renderNode(x, y, r, text) {
this.ctx.beginPath();
this.ctx.arc(x, y, r, 0, Math.PI * 2); // 绘制圆
this.ctx.stroke();
this.setStyle(this.ctx) // 设置样式
this.ctx.fillText(text, x, y); // 填充节点的value
}
renderLine(x1, y1, x2, y2) {
this.ctx.moveTo(x1, y1);
this.ctx.lineTo(x2, y2);
this.ctx.stroke();
}
}
绘制器
class TreeDrawer {
constructor(render) {
this.render = render;
}
layout(root, nodeW, nodeH) {
function computeWidth(root) {
if (!root) return 0;
if (!(root.left || root.right)) {
root.width = nodeW;
return root.width;
}
root.width = computeWidth(root.left) + computeWidth(root.right) + nodeW;
return root.width;
}
function computePosition(root, left, right, curY = nodeH) {
if (!root) return;
let x;
if (root.left) {
x = left + root.left.width + nodeW;
} else {
x = left + nodeW;
}
root.position = [x, curY];
computePosition(root.left, left, x, curY + nodeH);
computePosition(root.right, x, right, curY + nodeH);
}
computeWidth(root);
computePosition(root, 0, root.width);
return root.width;
}
draw(root, nodeW = 40, nodeH = 40) {
let depth = getTreeDepth(root) // 获取深度
let width = this.layout(root, nodeW, nodeH); // canvas宽度
let height = depth * nodeH + nodeH; // canvas高度
this.render.initSize(width + nodeW, height);
const getVector = (x, y, x1, y1) => {
let dis = Math.sqrt((x - x1) ** 2 + (y - y1) ** 2);
return [
(x1 - x) / dis,
(y1 - y) / dis
]
}
const linkNode = (root, child) => {
let [px, py] = root.position;
let [cx, cy] = child.position;
let [dx, dy] = getVector(px, py, cx, cy);
this.render.renderLine(px + (nodeW / 2) * dx, py + (nodeW / 2) * dy,
cx - (nodeW / 2) * dx, cy - (nodeW / 2) * dy)
}
const drawNode = (root) => {
let [x, y] = root.position;
this.render.renderNode(x, y, nodeW / 2, root.val);
}
function draw(root) {
if (!root) return;
drawNode(root);
if (root.left) linkNode(root, root.left);
if (root.right) linkNode(root, root.right);
draw(root.left);
draw(root.right);
}
draw(root);
}
}