二叉树的基本概念
1. 什么是二叉树
- 二叉树: 是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树
- 二叉搜索树:
- 是一棵空树,
- 或者是具有下列性质的二叉树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- . 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
- 如何判断一颗树是二叉搜索树
- 二叉树的中序一定是递增关系
- 二叉树的左子树范围 – (root.val, -infinity)
二叉树的右子树范围 – (root.val, infinity)
2. 二叉树的优点和缺点
优点
- 二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势
- 二叉树遍历的时间复杂度为O(N)
- 二叉搜索树的查找时间复杂度为O(logN)
缺点
- 在某些情况下,二叉树会成为一个链表
3. 二叉树的基本名词
- 根(root)
- 高度(height): 最远处叶子节点深度为0,往上递增
- 深度(depth): root深度为0,往下递增
- 层数: root为第一层,往下递增)
- 节点的度(degree):一个节点拥有子树的数量
- 节点(叶子节点,分支节点)
1. 叶子节点(leaf):没有子树的节点(度为0)
2.分支节点(inner node): 度不为0的节点- 子树: 树中任何一个节点作为根,它下面的所有节点为子树。所以一个二叉树有O(n)个子树
4. 二叉树的性质
性质1: 二叉树的第i层最多有 2(i-1)个节点 (i >= 1)
证明:
Base Case:
第一层: 2(1-1)= 1.
第二层: 2(2-1)= 2
Assumption:
假设第k-1层最多有节点2(k-2),则需证明第k层最多有2(k-1) 个节点
Proof:
因为每个节点最多有两个子节点, 则第k层最多有节点 2*2(k-2) = 2 (k-1)
性质2: 深度为k(k>=0)的二叉树最少有k个结点,最多有2k- 1个结点
证明:
在具有相同深度的二叉树中,仅当每一层都含有最大结点数时,其树中结点数最多。因此利用性质1可得,深度为k的二叉树的结点数至多为:
20+21+…+ 2(k-1) = 2k-1
所以如果一个二叉树有N个节点, 则最少有 log(N+1)层
2k+1 = N
2k = N+1
k = log(N+1)
性质3:在任意-棵二叉树中,若终端结点(叶子节点)的个数为n0,度为2的结点数为n2,则n0=n2+1。
证明*:
- 因为二叉树中所有结点的度数均不大于2,所以结点总数(记为n)应等于0度结点数、1度结点(记为n1)和2度结点数之和: n=n0+n1+n2 (eq1) (eq1)
- 另一方面,1度结点有一个孩子,2度结点有两个孩子,故二叉树中孩子结点总数是: n1+2*n2
- 树中只有根结点不是任何结点的孩子,故二叉树中的结点总数又可表示为:n=n1+2*n2+1 (eq2)
- 由式子1和式子2得到:n0=n2+1
5. 特别的二叉树
满二叉树 (Full Binary Tree)
国际定义:A binary tree in which each node has exactly zero or two children.In other words,every node is either a leaf or has two children
国内定义:
1、一个层数为k 的满二叉树总结点数为:2k-1
2、第i层上的结点数为:2i-1
3、一个层数为k的满二叉树的叶子结点个数(也就是最后一层):2k-1
完全二叉树 (Complete Binary Tree)
若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这就是完全二叉树。
下图就不是完全二叉树
平衡二叉树 (Balance Binary Tree)
平衡树(Balance Tree,BT) 指的是,任意节点的子树的高度差都小于等于1
下图就不是平衡二叉树
判断是否是平衡二叉树
// An highlighted block
class Solution {
boolean isBalanced = true;
public boolean isBalanced(TreeNode root) {
helper(root, 0);
return isBalanced;
}
public int helper(TreeNode root, int count) {
if(root == null) {
return 0; //代表已经走到尽头,高度返回为0
}
int leftCount = helper(root.left, count); //计算左子树的高度
int rightCount = helper(root.right, count); //计算右子树的高度
if (leftCount - rightCount > 1 || leftCount - rightCount < -1) {
//如果左右子树的高度相差超过1,则说明不是平衡二叉树,将isBalanced设置为false
isBalanced = false;
}
//选出左右子树中最高的子树,然后加1得出目前节点的高度
count = (leftCount > rightCount) ? leftCount + 1 : rightCount + 1;
return count;
}
}
二叉树的基本方法
1.插入方法
递归
public TreeNode insertHelper(TreeNode root, int target) {
if(root == null) {
return new TreeNode(target);
}
if(target < root.val) {
root.left = insertHelper(root.left, target);
}
if(target > root.val) {
root.right = insertHelper(root.right, target);
}
return root;
}
迭代
public TreeNode insertIntoBST(TreeNode root, int val) {
TreeNode node = root;
//如果树为空
if(root == null) {
TreeNode temp = new TreeNode(val);
root = temp;
return root;
}
//正常情况
while(node.left != null || node.right != null) {
if(val < node.val) {
if (node.left == null) {
TreeNode temp = new TreeNode(val);
node.left = temp;
return root;
}
node = node.left;
}
else {
if (node.right == null) {
TreeNode temp = new TreeNode(val);
node.right = temp;
return root;
}
node = node.right;
}
}
//如果只有root时
TreeNode temp = new TreeNode(val);
if(val < node.val) {
node.left = temp;
}
else {
node.right = temp;
}
return root;
}
2. 查找方法
递归
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode queryBST(TreeNode root, int target) {
return queryHelper(root, target);
}
public TreeNode queryHelper(TreeNode root, int target) {
if(root == null) {
return null
}
if(root.val == target) {
return root
}
else if(target < root.val) {
root = queryHelper(root.left, target);
}
else if(target > root.val) {
root = queryHelper(root.right, target);
}
return root;
}
}
3. 删除方法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
return helper(root, key);
}
public TreeNode helper(TreeNode node, int key) {
if(node == null) {
return node;
}
//找到目标
if(node.val == key) {
//如果没有子节点,返回null
//目标node = null实现删除
if(node.left == null && node.right == null) {
return null;
}
//如果左节点为空,右节点不为空,返回node.right
//目标node = node.right 实现删除
if(node.left == null) {
return node.right;
}
//如果右节点为空,左节点不为空,返回node.left
//目标node = node.left 实现删除
if(node.right == null) {
return node.left;
}
//如果左右节点都不为空
else {
//在目标节点的左子树中找到最大值
TreeNode temp = findMax(node.left);
//将目标节点的值替换为左子树中的最大值
node.val = temp.val;
//然后将左子树中最大值节点删除
node.left = helper(node.left, temp.val);
return node;
}
}
//没有找到目标,继续查找
if(key < node.val) {
node.left = helper(node.left, key);
}
else {
node.right = helper(node.right, key);
}
return node;
}
//找到最大值
public TreeNode findMax(TreeNode node) {
if (node.right == null) {
return node;
}
return findMax(node.right);
}
}
二叉树的时间复杂度分析
普通二叉树 | 平衡二叉树 | |
---|---|---|
查找 | O(N) | O(logN) |
插入 | O(N) | O(logN) |
删除 | O(N) | O(logN) |
Note 1: 普通二叉树在最坏情况下是一个链表,所以时间复杂度是O(N)
Note 2: 平衡二叉树在最坏情况下操作次数不超过树的深度,时间复杂度是O(logN).
二叉树的遍历
1. 二叉树的遍历
前序 (DFS):根左右
- 前序的第一个元素是根
- 下图前序顺序:62, 41,28,19,17,16,15,30,22,13
中序: 左根右
- 二叉搜索树的中序遍历是有序递增线性结构
- 二叉树搜索树的中序的倒序是有序递减线性结构
- 下图中序顺序:19, 28, 17, 41, 15, 16, 62, 22, 30, 13
后序: 左右根
- 后序的最后一个元素是根
- 下图后序顺序:19,17,28,15, 16, 41 22, 13, 30, 62
层序 (BFS): 按层输出
- 下图层序顺序:[62], [41,30], [28,16,22,13], [19,17,15]
2. 二叉树遍历的实现
递归方法 (时间复杂度O(N), 空间复杂度O(h))
前序, 中序,后序
其中前序是二叉树的DFS
层序
递归方法使用DFS, 迭代方法使用BFS
迭代方法
前序
中序
后序
一般方法为:根左右(前序)-> 根右左 -> 左右根(后序)
层序
Morris 遍历(意义不大,不推荐使用)
特性
- 递归和迭代中都借助了额外的空间(栈空间)
- Morris遍历可以不使用额外的空间
- 改变了原有的数据,在实践中不推荐使用
Morris 前序遍历
- 右子树挂在左子树最右节点
- morris前序遍历可以将树转换为链表
Morris 中序遍历
- 根节点挂在左子树的最右节点
- Morris中序不能将树转换为链表
Morris 后序遍历
- 思路和递归方法一样,从根左右 — 根右左 — 左右根
- 左子树挂在右子树最左节点 ----然后倒序输出
public class Solution {
/**
* @param root: A Tree
* @return: Postorder in ArrayList which contains node values.
*/
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> nums = new ArrayList<>();
TreeNode cur = null;
while (root != null) {
if (root.right != null) {
cur = root.right;
while (cur.left != null && cur.left != root) {
cur = cur.left;
}
if (cur.left == root) {
cur.left = null;
root = root.left;
} else {
nums.add(root.val);
cur.left = root;
root = root.right;
}
} else {
nums.add(root.val);
root = root.left;
}
}
Collections.reverse(nums);
return nums;
}
}