目录
- 一、概念
- 二、 两种特殊的二叉树
- 三、 二叉树的性质
- 四、二叉树的存储
- 五、二叉树的基本操作
- 1、二叉树的遍历
- (1)前中后序遍历
- (2)层序遍历
- 2、基本操作
- 六、总结
一、概念
一棵二叉树是结点的一个有限集合,该集合:
- 或者为空
- 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二、 两种特殊的二叉树
- 满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。也就是说,如果一棵二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
- 完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
三、 二叉树的性质
- 若规定 根节点的层数为 1 ,则一棵 非空二叉树的第 i 层上最多有 2^(i-1) (i>0) 个结点;
- 若规定只有 根节点的二叉树的深度为 1 ,则 深度为 K 的二叉树的最大结点数是2^k - 1
(k>=0); - 对任何一棵二叉树 , 如果其 叶结点个数为 n0, 度为 2 的非叶结点个数为 n2, 则有 n0 = n2 + 1;
假设二叉树有N个节点,一棵二叉树,有n0(度为0的节点),n1(度为1的节点),n2(度为2)有N=n0+n1+n2;
一个有N个节点的二叉树,共有N-1条边;
度为0的节点产生0条边,度为1的节点有n1个 产生n1条边,度为2的节点有n2个 产生2n2条边,N-1=n1+2n2;
联立得n0=n2+1;
对于任意一个二叉树,叶子节点的个数比度为2的节点多一个。 - 具有 n 个结点的完全二叉树的深度 k 为log₂(n+1) 上取整;
2^k-1=n->k=log₂(n+1) - 对于具有 n 个结点的完全二叉树 ,如果按照 从上至下从左至右的顺序对所有节点从 0 开始编号 ,则对于 序号为 i 的结点有 :
若 i>0 , 双亲序号: (i-1)/2 ; i=0 , i 为根节点编号 ,无双亲节点
若 2i+1<n ,左孩子序号: 2i+1 ,否则无左孩子;
若 2i+2<n ,右孩子序号: 2i+2 ,否则无右孩子。
四、二叉树的存储
二叉树的存储结构分为:顺序存储和类似于链表的链式存储。
二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:
// 孩子表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
// 孩子双亲表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
Node parent; // 当前节点的根节点
}
五、二叉树的基本操作
1、二叉树的遍历
(1)前中后序遍历
NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点—>根的左子树—>根的右子树。
LNR:中序遍历(Inorder Traversal)——根的左子树—>根节点—>根的右子树。
LRN:后序遍历(Postorder Traversal)——根的左子树—>根的右子树—>根节点。
// 前序遍历
void preOrder(Node root);
// 中序遍历
void inOrder(Node root);
// 后序遍历
void postOrder(Node root);
前序遍历
先输出当前节点(初始的时候是root节点)
如果左子节点不为空,则递归继续前序遍历
如果右子节点不为空,则递归继续前序遍历
中序遍历
如果当前节点的左子节点不为空,则递归中序遍历
输出当前节点
如果当前的右子节点不为空,则递归中序遍历
后序遍历
如果当前节点的左子节点不为空,则递归后序遍历
如果当前节点的右子节点不为空,则递归后序遍历
输出当前节点
// 前序遍历
void preOrder(Node root) {
if (root == null) {
return;
}
// 访问根节点
System.out.print(root.data + " ");
// 递归遍历左子树
preOrder(root.left);
// 递归遍历右子树
preOrder(root.right);
}
// 中序遍历
void inOrder(Node root) {
if (root == null) {
return;
}
// 递归遍历左子树
inOrder(root.left);
// 访问根节点
System.out.print(root.data + " ");
// 递归遍历右子树
inOrder(root.right);
}
// 后序遍历
void postOrder(Node root) {
if (root == null) {
return;
}
// 递归遍历左子树
postOrder(root.left);
// 递归遍历右子树
postOrder(root.right);
// 访问根节点
System.out.print(root.data + " ");
}
(2)层序遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在
层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层
上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
public void levelOrder(TreeNode root){
Queue<TreeNode> queue = new LinkedList<>();
if(root != null){
queue.offer(root);
}
while (!queue.isEmpty()){
TreeNode top = queue.poll();
System.out.print(top.val+" ");
if(top.left != null){
queue.offer(top.left);
}
if(top.right != null){
queue.offer(top.right);
}
}
}
2、基本操作
(1)获取二叉树结点个数:
public int size(Node root){
if(root==null)
return 0;
return size(root.left)+size(root.right)+1;
}
(2)获取叶子结点个数:
// 获取叶子节点的个数
public int getLeafNodeCount(Node root){
if(root==null){
return 0;
}
if(root.left==null&&root.right==null){
return 1;
}
return getLeafNodeCount(root.left)+getLeafNodeCount(root.right);
}
(3) 获取第k层结点个数:
public int getKLevelNodeCount(TreeNode root, int k){
if(root == null){
return 0;
}
if(k == 1){
return 1;
}
return getKLevelNodeCount(root.left,k-1) + getKLevelNodeCount(root.right,k-1);
}
(4) 获取二叉树高度:
public int getHeight(TreeNode root){
if(root == null){
return 0;
}
int leftH = getHeight(root.left);
int rightH = getHeight(root.right);
return (leftH > rightH ? leftH :rightH) + 1;
}
(5)检测是否存在value值:
public TreeNode find(TreeNode root,int val){
if(root == null) return null;
if(root.val == val){
return root;
}
TreeNode leftL = find(root.left,val);
if(leftL != null){
return leftL;
}
TreeNode leftLR = find(root.right,val);
if(leftLR != null){
return leftLR;
}
return null;
}
(6)是否为完全二叉树:
public boolean isCompleteTree(TreeNode root){
Queue<Node> queue = new LinkedList<>();
if(root != null){
queue.offer(root);
}
while (!queue.isEmpty()){
Node cur = queue.poll();
if(cur != null){
queue.offer(cur.left);
queue.offer(cur.right);
}else{
break;
}
}
while (!queue.isEmpty()){
Node cur = queue.poll();
if(cur != null){
return false;
}
}
return true;
}
六、总结
通过对二叉树的深入学习,我们可以理解其性质、存储方式以及基本操作,从而在实际问题中灵活运用二叉树来解决各种问题。在实际应用中,我们还需要根据具体问题选择合适的二叉树类型(如满二叉树、完全二叉树、平衡二叉树等)以及相应的存储方式(顺序存储或链式存储)。此外,二叉树的遍历是二叉树操作的基础,需要熟练掌握前序、中序、后序和层序遍历的方法。通过不断练习和实践,我们可以更好地应用二叉树来解决实际问题。