简介
二叉树(Binary Tree):计算机中数据结构的一种,是树形结构的一个重要类型。结构类型是每个节点最多有两个子树的树结构。特点是每个节点最多只能有了两棵子树,且有左右之分。
定义
二叉树是指树中节点的度不大于2的有序树,是n(n>=0)个节点的有限集合,该集合或者为空集(称为空二叉树),或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树 。
相关术语
- 叶子节点:也称为终端节点,没有子树的节点或者度为零的节点。
- 节点的关系:节点子树的根节点为该节点的孩子节点。相应该节点称为孩子节点的双亲节点。
- 节点的度:一个节点拥有子树的数目称为节点的度
- 节点的层级:从根节点开始,假设根节点为第1层,根节点的子节点为第2层,依此类推,如果某一个节点位于第L层,则其子节点位于第L+1层
- 数的深度:也称为树的高度,树中所有节点的层次最大值称为树的深度
基本形态
- 空二叉树
- 只有一个跟节点的二叉树
- 只有左子树
- 只有右子树
- 满二叉树
特殊类型
- 满二叉树 :如果一棵二叉树只有度为0的节点和度为2的节点,并且度为0的节点在同一层上,则这棵二叉树为满二叉树。一棵深度为k且有个结点的二叉树称为满二叉树
- 完全二叉树 :深度为k,有n个节点的二叉树当且仅当其每一个节点都与深度为k的满二叉树中编号从1到n的节点一一对应时,称为完全二叉树
二叉树遍历
例图
- 先序遍历:根节点 –> 左子树 –> 右子树 ==> A 、B 、D 、E 、C 、F 、G
- 中序遍历 :左子树 –> 根节点 –> 右子树 ==> D、B、E、A 、F 、C、G
- 后序遍历:左子树 –> 右子树 –> 根节点 ==> D、E、B、F、G、C、A
- 层级遍历:也叫层序遍历,按层遍历 ==> A、B、C、D、E、F、G
代码实现
递归实现
1 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
| let result = []; function recursive(res,root){ if (root == null){ return []; } result.push(root.val); recursive(result,root.left); recursive(result,root.right); }
let result = []; function recursive(result,root){ if(root == null){ return []; } recursive(result,root.left); result.push(root.val); recursive(result,root.right); }
let result = []; function recursive(result,root){ if(root == null){ return []; } recursive(result,root.left); recursive(result,root.right); result.push(root.val); }
|
非递归实现
非递归实现,其实就是迭代方法,引入栈来替代递归。由于栈的特性是后进先出,所以进栈的时机和递归是相反的 。所以利用递归的思路用栈实现时,需要先放右节点进栈,再放左节点进栈。这样可以保证每次出栈取到的节点都是左节点优先,达到和递归顺序一样的效果。
先序遍历
先序遍历的顺序:根–左–右
所以每次访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var preorderTraversal = function(root) { if (root == null){ return []; } let stack = []; let result = []; let node = root; while(stack.length > 0 || node != null){ if(node != null){ result.push(node.val); stack.push(node); node = node.left; }else{ node = stack.pop(); node = node.right; } } return result; };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var preorderTraversal = function(root) { if (root == null){ return []; } let stack = [root]; let result = []; while(stack.length > 0){ let node = stack.pop(); if(node.right != null){ stack.push(node.right); } if(node.left != null){ stack.push(node.left); } result.push(node.val); } return result; };
|
中序遍历
中序遍历是以 左–根–右 遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var inorderTraversal = function(root) { if (root == null){ return []; } let stack = []; let result = []; let node = root; while(stack.length > 0 || node != null){ if(node != null){ stack.push(node); node = node.left; }else{ node = stack.pop(); result.push(node.val); node = node.right; } } return result; };
|
后序遍历
思路:
对于任意节点P而言
P如果是叶子节点,直接输出
P如果有孩子,且孩子没有被访问过,则按照右孩子,左孩子的顺序依次入栈
P如果有孩子,而且孩子都已经访问过,则访问P节点
1 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
| var postorderTraversal = function(root) { if (root == null){ return []; } let stack = [root]; let result = []; let node; let preNode = null; while(stack.length > 0){ node = stack[stack.length-1]; let visited = preNode != null && (preNode == node.left || preNode == node.right); if((node.left == null && node.right == null) || visited){ result.push(node.val); preNode = node; stack.pop(); }else{ if(node.right != null){ stack.push(node.right); } if(node.left != null){ stack.push(node.left); } } } return result; };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| var postorderTraversal = function(root) { if (root == null){ return []; } let result = []; let stack = []; let node = root; while(node != null || stack.length > 0){ if(node != null){ stack.push(node); result.push(node.val); node = node.right; }else{ node = stack.pop(); node = node.left; } } return result.reverse(); };
for(result.length > 0){ node = result.pop(); }
|
技巧性解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var postorderTraversal = function(root) { if (root == null){ return []; } let result = []; let stack = [root]; while(stack.length > 0){ let node = stack.pop(); result.push(node.val); if(node.left != null){ stack.push(node.left); } if(node.right != null){ stack.push(node.right); } } return result.reverse(); };
|
递归和迭代的时间复杂度和空间复杂度都是O(n) ,
区别:在于递归的空间是系统栈维护的
时间复杂度的推导公式:Master公式,Master公式是常用来解决递归问题时间复杂度的通用公式
1 2 3
| 公式 T(N) = a*T(N / b) + O (N^d) 代入公式得到:T(N)=2T(N/2)+O(1):,其中 a = 2, b = 2, d = 0; 得到 log(2,2) = 1 > 0,代入公式 O(N ^ log(b,a)) = O(N^ log(2,2)) = O(N)
|
层级遍历(BFS)
1 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
| var levelOrderBottom = function(root) { if(root == null){ return []; } let result = []; let queue = []; queue.push(root); let node; while (queue.length > 0) { let count = queue.length; let list = []; for(let i = 0; i < count; i++){ node = queue.shift(); list.push(node.val); if(node.left != null){ queue.push(node.left); } if(node.right != null){ queue.push(node.right); } } result.push(list); } return result; };
|
二叉树深度 (BFS and DFS)