如何定义树节点
// 这里定义一个树节点
// 包含:节点值、子左节点、子右节点
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
如何计算一个树节点的深度
- 定义:从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
var maxDepth = function (root) {
// 节点为空则不计数
if (root === null) return 0;
// 递归一次计数一次
// 并且记录当前子节点最大深度
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
};
- 时间复杂度:我们每个结点只访问一次,因此时间复杂度为
O(n), 其中n是结点的数量。 - 空间复杂度:在最糟糕的情况下,树是完全不平衡的,例如每个结点只剩下左子结点,递归将会被调用
N次(树的高度),因此保持调用栈的存储将是O(n)。但在最好的情况下(树是完全平衡的),树的高度将是log(n)。因此,在这种情况下的空间复杂度将是O(log(n))。
如何判断一个树为平衡二叉树
-
定义:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
-
首先,需要获取一个节点的高度
下图摘自 leetcode
var getRootHeight = function (node) {
// 节点为 null 则取消节点计数
if (node === null) return -1;
// 节点计数
// 当前子节点高度最大值
return 1 + Math.max(getRootHeight(node.left), getRootHeight(node.right));
}
- 其次,则可以使用递归计算当前树是否为平衡二叉树
var isBalanced = function (root) {
if (root == null) {
// 节点为空则平衡
return true;
} else if (Math.abs(getRootHeight(root.left) - getRootHeight(root.right)) > 1) {
// 节点高度差大于1,则不平衡
return false;
} else {
// 递归比较子节点
return isBalanced(root.left) && isBalanced(root.right);
}
};
- 时间复杂度:
O(n),计算每棵子树的高度和判断平衡操作都在恒定时间内完成。 - 空间复杂度:
O(n),如果树不平衡,递归栈可能达到O(n)。
如何判断一个树为对称二叉树
- 定义:
- 它们的两个根结点具有相同的值。
- 每个树的右子树都与另一个树的左子树镜像对称。
下图摘自 leetcode
- 首先,需要比较两个节点是否对称,如下思路:
- 左右节点是否都有值;
- 左右父节点值是否相等;
- 左右节点子左右值是否刚好相反;
var isMirror = function (node1, node2) {
// 节点同为空
if (node1 === null && node2 === null) return true;
// 节点其中之一为空
if (node1 === null || node2 === null) return false;
// 比较父节点值是否相等
// 比较子左右节点是否相反
return node1.val === node2.val && isMirror(node1.left, node2.right) && isMirror(node1.right, node2.left);
};
- 这个时候就能比较该树是否对称
var isSymmetric = function (root) {
// 比较该树自己是否对称
return isMirror(root, root);
};
- 时间复杂度:
O(n),因为我们遍历整个输入树一次,所以总的运行时间为O(n),其中 n 是树中结点的总数。 - 空间复杂度:递归调用的次数受树的高度限制。在最糟糕情况下,树是线性的,其高度为
O(n)。因此,在最糟糕的情况下,由栈上的递归调用造成的空间复杂度为O(n)。
树的遍历
- 先序遍历
- 访问根节点
- 遍历左子树
- 遍历右子树
var preorderTraversal = function (node, array) {
if (!node) return array;
// 先根节点
array.push(node.val);
// 子左节点
node.left && preorderTraversal(node.left, array);
// 子右节点
node.right && preorderTraversal(node.right, array);
return array;
};
- 中序遍历
- 遍历左子树
- 访问根节点
- 遍历右子树
var inorderTraversal = function (node, array) {
if (!node) return array;
// 先子左节点
node.left && inorderTraversal(node.left, array);
// 再根节点
array.push(node.val);
// 子右节点
node.right && inorderTraversal(node.right, array);
return array;
};
- 后序遍历
- 遍历左子树
- 遍历右子树
- 访问根节点
var postorderTraversal = function (node, array) {
if (!node) return array;
// 先子左节点
node.left && postorderTraversal(node.left, array);
// 子右节点
node.right && postorderTraversal(node.right, array);
// 再根节点
array.push(node.val);
return array;
};