二叉树
二叉树:即每个节点最多有两个子树的树状结构
二叉查找树,二叉排序树,二叉搜索树:左节点<根节点<右节点的数据结构
基本二叉树的构造函数
function Node(val) {
this.left = this.right = null
this.val = val
}
2
3
4
模拟二叉树数据(node1是根节点)
var node4 = {left: null, right: null, val: 4 };
var node5 = {left: null, right: null, val: 5 };
var node6 = {left: null, right: null, val: 6 };
var node7 = {left: null, right: null, val: 7 };
var node3 = {left: node6, right: node7, val: 3 };
var node2 = {left: node4, right: node5, val: 2 };
var node1 = {left: node2, right: node3, val: 1 };
实际结构如下:
// 1
// /\
// 2 3
// /\ /\
// 4 5 6 7
2
3
4
5
6
7
8
9
10
11
12
13
1.前序遍历
递归实现
function qianxubianli(node) {
if(!node) {
return
}
console.log(node.val)
node.left&&qianxubianli(node.left)
node.right&&qianxubianli(node.right)
}
qianxubianli(node1)
2
3
4
5
6
7
8
9
迭代实现
function qianxubianli(node) {
var stack = [];
var result = [];
stack.push(node);
while(stack.length) {
var item = stack.pop();
result.push(item.val);
if(item.right) {
stack.push(item.right)
}
if(item.left) {
stack.push(item.left);
}
}
console.log(result)
}
qianxubianli(node1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2.中序遍历
递归实现
function zhongxubianli(node) {
if(!node) {
return
}
node.left && zhongxubianli(node.left)
console.log(node.val)
node.right && zhongxubianli(node.right)
}
zhongxubianli(node1)
2
3
4
5
6
7
8
9
迭代实现
function zhongxubainli(node) {
var stack = [];
var result = [];
stack.push(node);
while(stack.length) {
var item = stack[stack.length-1];
if(item.left && !item.left.isOK) {
stack.push(item.left)
} else if(!item.left || item.left && item.left.isOK) {
var index = stack.pop();
result.push(index.val);
index.isOK = true;
item.right && stack.push(item.right);
}
}
console.log(result)
}
zhongxubainli(node1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3.后序遍历
递归实现
function houxubianli(node) {
if(!node) {
return
}
node.left && houxubianli(node.left)
node.right && houxubianli(node.right)
console.log(node.val)
}
houxubianli(node1)
2
3
4
5
6
7
8
9
迭代实现
function houxubianli(node) {
var stack = [];
var result = [];
stack.push(node);
while(stack.length) {
var item = stack[stack.length-1];
if(item.left && !item.left.isOK) {
stack.push(item.left)
} else if(item.right && !item.right.isOK) {
stack.push(item.right)
// 如果是叶子节点,或者子节点都输出了,则可以弹出该节点
} else if((!item.left || item.left && item.left.isOK) && (!item.right || item.right && item.right.isOK)) {
var index = stack.pop();
result.push(index.val);
index.isOK = true;
}
}
console.log(result)
}
houxubianli(node1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4.层次遍历
function cengcibianli(node) {
var queue = [];
var result = [];
queue.push(node);
while (queue.length) {
for (let i = 0; i < queue.length; i++) {
var item = queue.shift();
result.push(item.val);
item.left && queue.push(item.left);
item.right && queue.push(item.right);
}
}
console.log(result)
}
cengcibianli(node1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
5.剑指Offer
重建二叉树:给出前序和中序遍历重建这个二叉树,通过前序遍历获取根节点然后通过递归,找到前序遍历的左子树和中序遍历的左子树递归遍历(终止条件:左子树长度为0返回NULL),右子树相同。
二叉树镜像:利用前序遍历先获取根节点然后获取子节点进行替换,然后递归遍历左节点和右节点即可(当节点为NULL则返回即可)。
从上往下打印二叉树:对二叉树进行层次遍历即可。
二叉树中和为某一个值的路径:对二叉树进行前序遍历,并把路径作为函数参数传入,若该节点的值为传入参数值且是叶子节点,则推入结果数组中。
function findQuickPath(root, result, path, num) {
// 判断条件为节点值相同并且是叶子节点
if(root.val === num && root.right === null && root.left === null) {
result.push(path)
}
let myNum = num - root.val;
path.push(root.val);
if(root.left) {
// 使用浅拷贝保证数组不被修改
findQuickPath(root.left, result, path.slice(0), myNum)
}
if(root.right) {
findQuickPath(root.right, result, path.slice(0), myNum)
}
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
二叉树的深度:使用递归,注意返回值即可。层次遍历每到一层深度加1即可
function TreeDepth(pRoot) {
if (!pRoot) {
return 0
}
return Math.max(1 + TreeDepth(pRoot.left), 1 + TreeDepth(pRoot.right))
}
// 非递归
function TreeDepth(pRoot) {
if (!pRoot) {
return 0
}
var depth = 0;
var queue = [pRoot];
while(queue.length) {
depth++;
for(let i = 0; i < queue.length; i++) {
let item = queue.shift();
item.left && queue.push(item.left);
item.right && queue.push(item.right);
}
}
return depth
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
平衡二叉树:先求出左右节点的二叉树高度,如果相差小于1,则递归遍历左右子节点即可。可采用后序遍历减少比较的开销:
function isBalance(node) {
return getDepth(node) !== -1;
function getDepth(node) {
if (node === null) return 0;
let left = getDepth(node.left);
if (left === -1) return -1;
let right = getDepth(node.right);
if (right === -1) return -1;
return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
}
}
2
3
4
5
6
7
8
9
10
11
二叉树中序遍历的下一个节点:每个节点有一个next指针指向父节点
1、有右子树的,那么下个结点就是右子树最左边的点;
2、没有右子树的,也可以分成两类,a)是父节点左孩子 ,那么父节点就是下一个节点 ; b)是父节点的右孩子找他的父节点的父节点的父节点...直到当前结点是其父节点的左孩子位置。如果没有,那么他就是尾节点。
function GetNext(pNode) {
if (!pNode) {
return null
}
var result = null;
// 有右子树的,那么下个结点就是右子树最左边的点
if (pNode.right) {
result = pNode.right;
while (result.left) {
result = result.left;
}
} else {
// 没有右子树且是父节点右孩子
if (pNode.next && pNode.next.right === pNode) {
result = pNode.next;
// 一直找其父节点
while (result.next && result.next.right === result) {
result = result.next;
}
// 如果父节点为空,则返回空,不为空则代表当前节点是父节点的左节点则直接返回父节点
if (result.next === null) {
return null
} else {
result = result.next;
}
} else {
// 没有右子树且是父节点左孩子
result = pNode.next;
}
}
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
二叉树对称:通过递归判断每个节点的值是否都是null或者相等,null则返回true,相等则判断其左右节点的值是否相等。采用队列保存节点的左右节点,然后入队和出队都是成对出现,并进行判断即可。
function isSymmetrical(pRoot) {
if(pRoot === null) {
return true
}
var queue = [];
queue.push(pRoot.left);
queue.push(pRoot.right);
while(queue.length) {
var left = queue.shift();
var right = queue.shift();
if(left === null && right === null) continue;
if(left === null || right === null) return false;
if(left.val !== right.val) return false;
queue.push(left.left);
queue.push(right.right);
queue.push(left.right);
queue.push(right.left);
}
return true
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
之字形打印二叉树:层次遍历并用变量判断输出即可。
二叉树序列化和反序列化:采用递归做法很简单(每个空节点则打印#来表示)
function Serialize(pRoot) {
var stack = [];
var str = [];
stack.push(pRoot);
while (stack.length) {
let item = stack.pop();
if (!item) {
str.push('$');
continue;
}
str.push(item.val);
stack.push(item.right);
stack.push(item.left);
}
return str
}
function Deserialize(str) {
if(str.length <= 1) {
return null
}
var root = null;
var temp = str.shift();
if(typeof temp === 'number'){
root = new TreeNode(temp);
root.left = Deserialize(str);
root.right = Deserialize(str);
}
return root;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
采用层次遍历:
function Serialize(pRoot) {
var queue = [];
queue.push(pRoot);
var str = [];
while (queue.length) {
let item = queue.shift();
if (item === null) {
str.push('#');
continue;
}
str.push(item.val);
queue.push(item.left);
queue.push(item.right);
}
return str.join(', ')
}
function Deserialize(str) {
var arr = str.split(', ');
var newarr = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] === '#') {
newarr.push(null);
} else {
newarr.push(new TreeNode(arr[i]));
}
}
for (let i = 0, j = 1; j < newarr.length; i++) {
if (newarr[i] !== null) {
newarr[i].left = newarr[j++];
newarr[i].right = newarr[j++];
}
}
return newarr[0]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
采用前序遍历(反序列化逻辑有些难懂):
function Serialize(pRoot) {
var stack = [];
var str = [];
stack.push(pRoot);
while (stack.length) {
let item = stack.pop();
if (!item) {
str.push('#')
continue;
}
str.push(item.val);
stack.push(item.right);
stack.push(item.left);
}
return str.join(', ')
}
function Deserialize(str) {
if (str.length <= 1) {
return null
}
var arr = str.split(', ');
var stack = [];
var newarr = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== '#') {
newarr[i] = new TreeNode(arr[i]);
} else {
newarr[i] = null;
}
}
var root = newarr[0];
stack.push(newarr[0]);
let i = 1;
while (newarr[i] !== null) {
let len = stack.length;
stack[len - 1].left = newarr[i];
stack.push(newarr[i++]);
}
while (stack.length) {
stack.pop().right = newarr[++i];
if (newarr[i] !== null) {
stack.push(newarr[i++]);
while (newarr[i] !== null) {
let len = stack.length;
stack[len - 1].left = newarr[i];
stack.push(newarr[i++]);
}
}
}
return root;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
总结
- 二叉树的深度优先遍历的非递归的通用做法是采用栈,广度优先遍历的非递归的通用做法是采用队列。
- 深度优先对每一个可能的分支路径深入到不能再深入为止,先序遍历、中序遍历、后序遍历属于深度优先。
- 广度优先又叫层次遍历,从上往下,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止。