二叉树
一:术语、定义、特点
1、树
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。
树结构中的术语
-
节点的度:节点拥有的子树的数目;
-
叶子节点:度为0的节点;
-
分支结点:度不为0的节点;
-
树的度:树中节点的最大的度;
-
层次:根结点的层次为1,其余节点的层次等于该结点的双亲节点的层次加1;
-
树的高度:树中节点的最大层次;
-
森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。
2、二叉树
二叉树有五种基本形态,如下图:
2.1 二叉树结构特点
- 每个节点最多有两个子树(即二叉树的度不能大于2),并且二叉树的子树有左右之分,其次序不能颠倒;
- 没有父节点的节点称为根节点;
- 没有子节点的节点称为叶子节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树;
- 树里面没有环路。
2.2 二叉树性质
性质1:在二叉树的第 i 层上最多有 2^(i – 1) 个节点; (i>=1)
性质2:深度为 k 的二叉树至多有 2^k – 1 个节点;(k>=1)
性质3:包含n个节点的二叉树的高度至少为(log2n)+1;
性质4:在任意一棵二叉树中,若终端节点的个数为n0,度为2的节点数为n2,则n0=n2+1;
考试时常考性质4的证明
性质4:在任意一棵二叉树中,若终端节点的个数为n0,度为2的节点数为n2,则n0=n2+1。
证明:因为二叉树中所有节点的度数均不大于2,不妨设n0表示度为0的节点个数,n1表示度为1的节点个数,n2表示度为2的节点个数。三类节点加起来为总结点个数,于是便可得到:n=n0+n1+n2 (式1)
由度之间的关系可得第二个等式:n=n00 + n11 + n2*2 + 1,即n=n1+2n2+1 (式2)
合(式1)(式2),移项可得:n0=n2+1,证毕。
3、几种特殊形态的二叉树
3.1 满二叉树
深度为k 且有2^k -1 个节点的二叉树。
∴
已知某二叉树深度为k,则该树最多有2^k - 1个节点;
每层有2^(k-1) 个节点;
3.2 完全二叉树
深度为k,有n个节点的二叉树,其每一个节点都与深度为k 的满二叉树中编号从1 至n 的节点一一对应。
特点:叶子节点只能出现在最下层和次下层,且最下层的叶子节点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,反之不然。
问:如果一个完全二叉树的节点总数为768个,求叶子节点的个数。
答:首先写出公式:768=n0+n1+n2,由二叉树的性质知:n0=n2+1,将之带入前式:768=n1+2n2+1,因为完全二叉树度为1的节点个数要么为0,要么为1,那么就把n1=0或1分别代入公式,发现n1=1才符合条件。从而n2=383,所以叶子节点个数n0=n2+1=384。
总结:如果一棵完全二叉树的节点总数为n,那么叶子节点等于n/2(当n为偶数时)或者(n+1)/2(当n为奇数时)
3.3 平衡二叉树
条件:当且仅当两个子树的高度差不超过1。
3.4 二叉搜索树
又称二叉查找树,节点值域满足:左子树<=根节点<=右子树。
(1)若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值。
(2)任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值。
(3)任意节点的左、右子树也分别为二叉查找树。
二:二叉树的代码实现
力扣上二叉树的题,要熟练掌握深搜和广搜。
1、创建节点
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
Node *node_create(type_t data){
Node *p = (Node *)malloc(sizeof(Node));
p->data = data;
p->left = NULL;
p->right = NULL;
return p;
}
2、二叉树的三序遍历(递归+迭代)
2.1 前序遍历
2.1.1 递归法
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right)
2.1.2 迭代法
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
pre_stack = []
ans = []
cur = root
while pre_stack or cur:
while cur:
ans.append(cur.val)
pre_stack.append(cur)
cur = cur.left
temp = pre_stack.pop()
cur = temp.right
return ans
迭代另一种写法:
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
pre_stack = [root]
ans = []
while pre_stack:
cur = pre_stack.pop()
ans.append(cur.val)
if cur.right:
pre_stack.append(cur.right)
if cur.left:
pre_stack.append(cur.left)
return ans
2.2 中序遍历
2.2.1 递归法
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)
2.2.2 迭代法
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
mid_stack = []
ans = []
cur = root
while cur or mid_stack:
while cur:
mid_stack.append(cur)
cur = cur.left
temp = mid_stack.pop()
ans.append(temp.val)
cur = temp.right
return ans
2.3 后序遍历
2.3.1 递归法
def postorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
return self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val]
2.3.2 迭代法
def postorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
ans = []
post_stack = []
cur = root
while post_stack or cur:
while cur:
ans.append(cur.val)
post_stack.append(cur)
cur = cur.right
temp = post_stack.pop()
cur = temp.left
return ans[::-1]
3、层序遍历
3.1 递归法
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
if not root:
return []
ans = []
self.dfs(1, root, ans)
return ans
def dfs(self, level, root, ans):
if len(ans) < level:
ans.append([])
ans[level - 1].append(root.val)
if root.left:
self.dfs(level + 1, root.left, ans)
if root.right:
self.dfs(level + 1, root.right, ans)
return ans
3.2 迭代法
思路:相当于广度优先搜索,使用队列实现。
队列初始化,将根节点压入队列。
当队列不为空,进行如下操作:出队一个节点,访问,若左子节点或右子节点不为空,将其压入队列。
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
if not root:
return []
level_queue = []
ans = []
level_queue.append(root)
while level_queue:
num = len(level_queue)
temp = []
while num > 0:
cur = level_queue.pop(0)
temp.append(cur.val)
if cur.left:
level_queue.append(cur.left)
if cur.right:
level_queue.append(cur.right)
num -= 1
ans.append(temp)
return ans
4、求二叉树中的节点数
递归思路:
(1)如果二叉树为空,节点个数为0;
(2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1;
def countNodes(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
return self.countNodes(root.left) + self.countNodes(root.right) + 1
5、求二叉树的深度
递归思路:
(1)如果二叉树为空,二叉树的深度为0;
(2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1;
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
depthLeft = self.maxDepth(root.left)
depthRight = self.maxDepth(root.right)
return depthLeft + 1 if depthLeft > depthRight else depthRight + 1
6、判断二叉树是否是平衡二叉树
递归思路:
(1)如果二叉树为空,返回真;
(2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假;
# 判断平衡二叉树
def isBalanced(self,root):
"""
:typeroot:TreeNode
:rtype:bool
"""
if not root:
return True
resultLeft = self.isBalanced(root.left)
heightLeft = self.treeDepth(root.left)
resultRight = self.isBalanced(root.right)
heightRight = self.treeDepth(root.right)
#左子树和右子树都是AVL,并且高度相差不大于1,返回真
if resultLeft and resultRight and abs(heightLeft-heightRight) <= 1:
return True
else:
return False
#求二叉树的深度
def treeDepth(self,root):
if not root:
return 0
depthLeft = self.treeDepth(root.left)
depthRight = self.treeDepth(root.right)
return depthLeft + 1 if depthLeft > depthRight else depthRight + 1
7、判断两棵二叉树结构是否相同
不考虑数据内容。结构相同意味着对应的左子树和对应的右子树都结构相同。
递归思路:
(1)如果两棵二叉树都为空,返回真;
(2)如果两棵二叉树一棵为空,另一棵不为空,返回假;
(3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假;
bool StructureCmp(BinaryTreeNode * pRoot1, BinaryTreeNode * pRoot2){
// 都为空,返回真
if(pRoot1 == NULL && pRoot2 == NULL)
return true;
// 有一个为空,一个不为空,返回假
else if(pRoot1 == NULL || pRoot2 == NULL)
return false;
// 比较对应左子树
bool resultLeft = StructureCmp(pRoot1->m_pLeft, pRoot2->m_pLeft);
bool resultRight = StructureCmp(pRoot1->m_pRight, pRoot2->m_pRight); // 比较对应右子树
return (resultLeft && resultRight);
}
8、求二叉树第k层节点个数
递归思路:
(1)如果二叉树为空或者k<1返回0;
(2)如果二叉树不为空并且k==1,返回1;
(3)如果二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和;
int GetNodeNumKthLevel(BinaryTreeNode * pRoot, int k){
if(pRoot == NULL || k < 1)
return 0;
if(k == 1)
return 1;
// 左子树中k-1层的节点个数
int numLeft = GetNodeNumKthLevel(pRoot->m_pLeft, k-1);
// 右子树中k-1层的节点个数
int numRight = GetNodeNumKthLevel(pRoot->m_pRight, k-1);
return (numLeft + numRight);
}
9、求二叉树的叶子节点个数
递归思路:
(1)如果二叉树为空,返回0;
(2)如果二叉树不为空且左右子树为空,返回1;
(3)如果二叉树不为空,且左右子树不同时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数;
int GetLeafNodeNum(BinaryTreeNode * pRoot){
if(pRoot == NULL)
return 0;
if(pRoot->m_pLeft == NULL && pRoot->m_pRight == NULL)
return 1;
int numLeft = GetLeafNodeNum(pRoot->m_pLeft); // 左子树中叶节点的个数
int numRight = GetLeafNodeNum(pRoot->m_pRight); // 右子树中叶节点的个数
return (numLeft + numRight);
}
10、求二叉树的镜像
递归思路:
(1)如果二叉树为空,返回空;
(2)如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树;
BinaryTreeNode * Mirror(BinaryTreeNode * pRoot){
if(pRoot == NULL) // 返回NULL
return NULL;
BinaryTreeNode * pLeft = Mirror(pRoot->m_pLeft); // 求左子树镜像
BinaryTreeNode * pRight = Mirror(pRoot->m_pRight); // 求右子树镜像
// 交换左子树和右子树
pRoot->m_pLeft = pRight;
pRoot->m_pRight = pLeft;
return pRoot;
}
11、求二叉树中两个节点的最近公共祖先节点
11.1 一般二叉树
python代码:
# 思路:
# 先特判:
# 当节点root为空时:return None;
# 当p或q的值域等于root时,return root;
# 再递归判断root的左、右子树;
# 若左右子树都不为空:return root;
# 若左真右假:return 左;
# 若左假右真:return 右;
# 否则,左右都为空:return None;
def lowestCommonAncestor(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
if not root:
return None
# if p.val == q.val:
# return p
if p.val == root.val or q.val == root.val:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right:
return root
elif left:
return left
elif right:
return right
else:
return None
方法二:迭代
先求从根节点到两个节点的路径,然后再比较对应路径的节点就行,最后一个相同的节点也就是他们在二叉树中的最低公共祖先节点。
bool GetNodePath(BinaryTreeNode * pRoot, BinaryTreeNode * pNode,
list<BinaryTreeNode *> & path){
if(pRoot == NULL)
return false;
if(pRoot == pNode){
path.push_back(pRoot);
return true;
}
path.push_back(pRoot);
bool found = false;
found = GetNodePath(pRoot->m_pLeft, pNode, path);
if(!found)
found = GetNodePath(pRoot->m_pRight, pNode, path);
if(!found)
path.pop_back();
return found;
}
BinaryTreeNode * GetLastCommonParent(BinaryTreeNode * pRoot, BinaryTreeNode * pNode1, BinaryTreeNode * pNode2){
if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
return NULL;
list<BinaryTreeNode*> path1;
bool bResult1 = GetNodePath(pRoot, pNode1, path1);
list<BinaryTreeNode*> path2;
bool bResult2 = GetNodePath(pRoot, pNode2, path2);
if(!bResult1 || !bResult2)
return NULL;
BinaryTreeNode * pLast = NULL;
list<BinaryTreeNode*>::const_iterator iter1 = path1.begin();
list<BinaryTreeNode*>::const_iterator iter2 = path2.begin();
while(iter1 != path1.end() && iter2 != path2.end()){
if(*iter1 == *iter2)
pLast = *iter1;
else
break;
iter1++;
iter2++;
}
return pLast;
}
11.2 二叉搜索树的最近公共祖先结点
方法一:递归
# 递归思路:
# 当p,q都在root的右子树中时,递归root.right并返回;
# 当p,q都在root的左子树中时,递归root.left并返回;
# 返回值:最近公共祖先root。
def lowestCommonAncestor(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
if p.val < root.val and q.val < root.val:
return self.lowestCommonAncestor(root.left, p, q)
if p.val > root.val and q.val > root.val:
return self.lowestCommonAncestor(root.right, p, q)
return root
方法二:迭代
# 迭代思路:
# 先特判:
# 当节点root为空时:return None;
# 当p、q值域相同时,return p 或 return q;
# 再遍历:
# 如果,当p,q都在root的右子树中,则遍历至root.right:root = root.right;
# 否则,当p,q都在root的左子树中,则遍历至root.left:root = root.left;
# 否则,说明找到了最近公共祖先,则return root。
def lowestCommonAncestor2(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
if not root:
return None
if p.val == q.val:
return p
while root:
if p.val > root.val and q.val > root.val:
root = root.right
elif p.val < root.val and q.val < root.val:
root = root.left
else:
return root
12、判断二叉树是否为完全二叉树
首先考虑完全二叉树的特点:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
可得思路:按层次(从上到下,从左到右)遍历二叉树,当遇到一个节点的左子树为空时,则该节点右子树必须为空,且后面遍历的节点左右子树都必须为空,否则不是完全二叉树。
bool IsCompleteBinaryTree(BinaryTreeNode * pRoot){
if(pRoot == NULL)
return false;
queue<BinaryTreeNode *> q;
q.push(pRoot);
bool mustHaveNoChild = false;
bool result = true;
while(!q.empty()){
BinaryTreeNode * pNode = q.front();
q.pop();
// 已经出现了有空子树的节点了,后面出现的必须为叶节点(左右子树都为空)
if(mustHaveNoChild){
if(pNode->m_pLeft != NULL || pNode->m_pRight != NULL){
result = false;
break;
}
}
else{
if(pNode->m_pLeft != NULL && pNode->m_pRight != NULL){
q.push(pNode->m_pLeft);
q.push(pNode->m_pRight);
}
else if(pNode->m_pLeft != NULL && pNode->m_pRight == NULL){
mustHaveNoChild = true;
q.push(pNode->m_pLeft);
}
else if(pNode->m_pLeft == NULL && pNode->m_pRight != NULL){
result = false;
break;
}
else{
mustHaveNoChild = true;
}
}
}
return result;
}
13、求二叉树的叶子结点
# DFS找二叉树的叶子节点
def dfs(self, root, res):
if not root:
return []
elif not root.left and not root.right:
res.append(root.val)
else:
self.dfs(root.left, res)
self.dfs(root.right, res)
return res
三、二叉树DFS和BFS代码模板
发现二叉树很多题都是用BFS和DFS做,觉得有必要总结一套代码模板。
所谓模板只是方便套用,按照具体题目要求来随机应变很重要。
3.1 二叉树BFS
def BFS(self, root):
# 特判:树根为空
if not root:
return []
# 返回值list
res = []
# 设置队列,初始时根入队
queue = [root]
# 开始遍历,循环条件:队列不为空
# 外层的while遍历树的层数
while queue:
# 获得当前队列的长度,即当前层节点的个数
lenQueue = len(queue)
# 内层for循环遍历当前层的所有节点
for i in range(lenQueue):
# 队首节点出队并读取其值域
node = queue.pop(0)
res.append(node.val)
# 将出队节点的子树入队
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return res
3.2 二叉树DFS
前序式
"""
前序遍历式DFS
"""
# 迭代式
def pre_DFS(self, root):
# 特判:树根为空
if not root:
return []
# 返回值
res = []
# 设置栈,特点是先进后出,初始时树根入栈
# 用栈存储下一步可能访问的节点
stack = []
cur = root
while stack or cur:
while cur:
res.append(cur.val)
# 栈是先进后出,所以先进栈右子树
stack.append(cur)
cur = cur.left
temp = stack.pop()
cur = temp.right
return res
# 递归式
def pre_DFS2(self, root):
# 特判:树根为空
if not root:
return []
# 返回值
res = []
res.append(root.val)
res += self.pre_DFS2(root.left)
res += self.pre_DFS2(root.right)
return res
中序式
"""
中序遍历式DFS
"""
# 迭代式
def mid_DFS(self, root):
# 特判:树根为空
if not root:
return []
# 返回值
res = []
# 设置栈,特点是先进后出,初始时树根入栈
# 用栈存储下一步可能访问的节点
stack = []
cur = root
while stack or cur:
while cur:
stack.append(cur)
cur = cur.left
res.append(cur.val)
temp = stack.pop()
cur = temp.right
return res
# 递归式
def mid_DFS2(self, root):
# 特判:树根为空
if not root:
return []
# 返回值
res = []
res += self.mid_DFS2(root.left)
res.append(root.val)
res += self.mid_DFS2(root.right)
return res
后序式
"""
后序遍历式DFS
"""
# 迭代式
def post_DFS(self, root):
# 特判:树根为空
if not root:
return []
# 返回值
res = []
cur = root
stack = []
while cur or stack:
while cur:
res.append(cur.val)
stack.append(cur)
cur = cur.right
temp = stack.pop()
cur = temp.left
return res[::-1]
# 递归式
def post_DFS2(self, root):
# 特判:树根为空
if not root:
return []
# 返回值
res = []
res += self.post_DFS2(root.left)
res += self.post_DFS2(root.right)
res.append(root.val)
return res