树节点类型
class TreeNode {
value: number
left?: TreeNode
right?: TreeNode
constructor(value: number, left?: TreeNode, right?: TreeNode){
this.value = value;
this.left = left;
this.right = right;
}
}
// 简写
class TreeNode {
constructor(public value: number,public left?: TreeNode,public right?: TreeNode){
}
}
二叉树:一个节点最多有两个子节点
- 高度Height:节点到叶子节点的最长路径(从下到上)
- 深度Depth:根节点到这个节点所经历的边的个数(从上到下)
- 层Level:节点的深度 + 1(计数起点为1)
二叉树:一个节点最多有两个子节点(完全二叉树、满二叉树)
如何存储
- 链式存储
- 顺序存储
如何构建一个二叉树,从根节点开始创建时不知道他对应的左树和右树,即new一个class,不好传参。
可以考虑从最底部的节点开始,直至创建到根节点。
const trees = {
value: 0,
left: {
value: 1,
left: {
value: 3
}
},
right: {
value: 2,
left: {
value: 4,
left: {
value: 6
},
right: {
value: 7
}
},
right: {
value: 5
}
}
}
const root = new TreeNode(0, new TreeNode(1), new TreeNode(2))
const node5 = new TreeNode(5)
const node7 = new TreeNode(7)
const node6 = new TreeNode(6)
const node6 = new TreeNode(4, node6, node7);
如何遍历上面的树做节点的记录或处理节点数据呢?
广度优先遍历(BFS)Breadth First Search
从树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。
代码如下:
function bfs(root: TreeNode) {
// 使用数组模拟队列。首先将根节点归入队列。当队列不为空的时候,执行循环:取出队列的一个节点,如果该结点的左子树为非空,则将该结点的左子树入队列;如果该结点的右子树为非空,则将该结点的右子树入队列。
const queue: TreeNode[] = [];
queue.push(root)
// 从上到下遍历二叉树的每一层
while (queue.length) {
const sz = queue.length
// 从左到右遍历每一层的每个节点
for (let i = 0; i < sz; i++) {
const curr = queue.shift()!
// 访问
console.log(curr.value)
// 将下一层节点放入队列
if (curr.left)
queue.push(curr.left)
if (curr.right)
queue.push(curr.right)
}
}
} // 0 > 1 > 2 > 3 > 4 > 5 > 6 > 7
执行后:
递归遍历/深度优先遍历(DFS)Depth First Search
对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.
定义下列字母:
D:访问根结点,L:遍历根结点的左子树,R:遍历根结点的右子树。
根据先访问节点数据定义可以有如下遍历(先左后右):
前序遍历:DLR
中序遍历:LDR
后序遍历:LRD
顺着字母表示的意思念下来就是遍历的顺序了
代码如下:时间复杂度O(n)
- 前序遍历:DLR
function preOrder(root?: TreeNode) {
if (!root) return
console.log(root.value)
preOrder(root.left)
preOrder(root.right)
}
// 0 > 1 > 3 > 2 > 4 > 6 > 7 > 5
- 中序遍历:LDR
function preOrder(root?: TreeNode) {
if (!root) return
preOrder(root.left)
console.log(root.value)
preOrder(root.right)
} // 3 > 1 > 0 > 6 > 4 > 7 > 2 > 5
- 后序遍历:LRD
function preOrder(root?: TreeNode) {
if (!root) return
preOrder(root.left)
preOrder(root.right)
console.log(root.value)
}
// 3 > 1 > 6 > 7 > 4 > 5 > 2 > 0
总结起来就是下面代码,只是处理逻辑的位置不同
function traverse(root?: TreeNode) {
if (!root) return
// 前序位置
traverse(root.left)
// 中序位置
traverse(root.right)
// 后序位置
}
无环单连通的树