二叉树深度优先遍历
概念
根据节点本身与后代节点的遍历先后关系可以分为:
- 先序遍历: 优先遍历节点本身,然后去遍历后代节点.
- 中序遍历: 先遍历左侧后代节点,然后遍历节点本身,然后遍历右侧后代节点.
- 后序遍历: 优先遍历后代节点,然后遍历节点本身.
中序遍历
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \ / \ / \
8 9 10 11 12 14 15 16
中序遍历的特性: 先遍历左侧后代节点,然后遍历节点本身,然后遍历右侧后代节点.
左侧优先
1 -> 2 -> 4 -> 8 (8最先被遍历)
[8]
递推父级
8 -> 4
[8,4]
递推右侧
4 -> 9
[8,4,9]
回归曾父级
9 -> (4) -> 2
[8,4,9,2]
左侧优先
2 -> 5 -> 10
[8,4,9,2,10]
递推父级
10 -> 5
[8,4,9,2,10,5]
递推右侧
5 -> 11
[8,4,9,2,10,5,11]
回归曾父级
11 -> (2) -> 1
[8,4,9,2,10,5,11,1]
左侧优先
1 -> 3 -> 6 -> 12
[8,4,9,2,10,5,11,1,12]
递推父级
12 -> 6
[8,4,9,2,10,5,11,1,12,6]
递推右侧
6 -> 14
[8,4,9,2,10,5,11,1,12,6,14]
回归曾父级
14 -> (6) -> 3
[8,4,9,2,10,5,11,1,12,6,14,3]
左侧优先
3 -> 7 -> 15
[8,4,9,2,10,5,11,1,12,6,14,3,15]
递推父级
15 -> 7
[8,4,9,2,10,5,11,1,12,6,14,3,15,7]
递推右侧
7 -> 16
[8,4,9,2,10,5,11,1,12,6,14,3,15,7,16]
结束
....
中序遍历的一个引申含义就是`左侧优先`,
所以数组的第一个节点,就是最左侧的叶子节点
先序遍历
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \ / \ / \
8 9 10 11 12 14 15 16
先序遍历的特性: 优先遍历节点本身,然后去遍历后代节点.
自身优先
1
[1]
左边次之
1 -> 2
[1,2]
自身优先
左边次之
2 -> 4
[1,2,4]
自身优先
左边次之
4 -> 8
[1,2,4,8]
自身优选
左边次之(null)
父级提升
右边最后
4 -> 9
[1,2,4,8,9]
父级提升
自身优先
2 -> 5
[1,2,4,8,9,5]
...
后续遍历
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \ / \ / \
8 9 10 11 12 14 15 16
后序遍历的特性: 优先遍历后代节点,然后遍历节点本身.
左边优先 右边次之 自身最后
左边优先
1 -> 2 -> 4 -> 8
[8]
右边次之
8 -> (4) -> 9
[8,9]
自身最后
9 -> 4
[8,9,4]
...
二叉树序列化和反序列化
广度优先原则实现
- 序列化
const serialize = (root) => {
/**
* queue操作原理:
* queue.shift 弹出 root
* queue.push 导入 left right
* res操作原理:
* res.push 导入 val
*/
const queue = [root];
let res = [];
while (queue.length) {
const node = queue.shift();
if (node) {
res.push(node.val);
queue.push(node.left)
queue.push(node.right);
} else {
res.push('X');
}
}
return res.join(',');
}
- 反序列化
const deserialize = (data) => {
/** 排空 */
if (data == 'X') return null;
/** 拆分 */
const list = data.split(',');
/**
* 初始化root queue cursor
* queue.shift() 弹出 node 并给node赋予leftNode rightNode (之所以需要参数是怕反复赋值)
* queue.push(leftNode)(rightNode) 收集 node 供下次赋值
* cursor 我们使用队列操作减少计算复杂度,使用索引. 从第二个元素开始,每次移动两步.
*/
const root = new TreeNode(list[0]);
const queue = [root];
let cursor = 1; // 第一个元素已经存在,所以现在只想第二个元素
while (cursor < list.length) {
const node = queue.shift();
/** 注意: 不要变量提升 */
const leftVal = list[cursor];
if (leftVal != 'X') {
/** 注意区分节点和节点的值 */
const leftNode = new TreeNode(leftVal);
node.left = leftNode;
queue.push(leftNode); // 放到队列里面等待等到后续调用的时候
}
const rightVal = list[cursor + 1];
if (rightVal != 'X') {
const rightNode = new TreeNode(rightVal);
node.right = rightNode;
queue.push(rightNode);
}
cursor += 2;
}
return root;
};
深度优先原则实现
- 序列化
/**
* 先序遍历
*/
const serialize = (root) => {
/** 递归边界 */
if (root == null) {
return 'X';
}
/** 递归(记得写const) */
const left = serialize(root.left);
const right = serialize(root.right);
/** 拼接 */
return root.val + ',' + left + ','+ right;
};
- 反序列化
const deserialize = (data) => {
/** 拆解 */
const list = data.split(',');
const buildTree = (list) => {
/** 弹出作为root.val */
const rootVal = list.shift();
/** 如果是X,则返回null */
if (rootVal == "X") {
return null;
}
/** 如果不是X,则返回一个节点 */
const root = new TreeNode(rootVal);
root.left = buildTree(list);
root.right = buildTree(list);
return root;
};
return buildTree(list);
};