二叉树基础知识
我们在 MySQL 为什么用 B+ 树实现索引 提到了二叉树的相关知识,本文就来详细分析一下二叉树。
概述
维基百科定义:
在计算机科学中,二叉树(英语:Binary tree)是每个节点最多只有两个分支(即不存在分支度大于2 的节点)的树结构。通常分支被称作 “左子树” 或 “右子树”。二叉树的分支具有左右次序,不能随意颠倒。
我的理解,一棵二叉树是 n 个有限节点的集合,该集合或者为空,或者是由一个根节点加上两棵互不相交的、分别称为左子树和右子树的二叉树组成。
特点:每个节点至多只有两棵子树(二叉树中不存在度大于2的节点)。
5 种基本形态
二叉树是递归定义的,其节点有左右子树之分,逻辑上二叉树有五种基本形态:
- 空二叉树——如图(a);
- 只有一个根节点的二叉树——如图(b);
- 只有左子树——如图(c);
- 只有右子树——如图(d);
- 既有左子树,又有右子树——如图(e)。
特殊的二叉树
满二叉树
除最后一层无任何子节点外,每一层上的所有节点都有两个子节点二叉树,这种二叉树就叫做满二叉树。
完全二叉树
一棵二叉树至多只有最下面的一层上的节点的度数可以小于 2,并且最下层上的节点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树。
完全二叉树除去最后一层节点为满二叉树。
还有一些常用的比如:平衡二叉树、二叉搜索树、红黑树等等。
术语
- 节点:包含一个数据元素及若干指向子树分支的信息
- 节点的度:一个节点拥有子树的数目
- 叶子节点:也称终端节点,没有子树的节点或者度为零的节点
- 分支节点:也称非终端节点,度不为零的节点
- 树的度:树中所有节点的度的最大值
- 节点的层次:从根节点开始,假设根节点为第1层,根节点的子节点为第2层,依此类推,如果某一个节点位于第L层,则其子节点位于第L+1层
- 树的深度:也称树的高度,树中所有节点的层次最大值
- 有序树:如果树中各棵子树的次序都是有先后次序,称为有序树
- 无序树:反之,如果树中各棵子树的次序没有先后次序,则称该树为无序树
- 森林:由m(m≥0)棵互不相交的树构成一片森林。如果把一棵非空的树的根节点删除,则该树就变成了一片森林,森林中的树由原来根节点的各棵子树构成 。
术语解释
节点
节点是数据结构中的基础,是构成复杂数据结构的基本组成单位。
节点 A 在图中表示为:
节点的度
一个节点拥有子树的数目。
叶子节点
也称终端节点,没有子树的节点或者度为零的节点。
图中 D 和 C 都是叶子节点。
分支节点
也称非终端节点,度不为零的节点。
上图中 A 和 B 都是分支节点。
树的度
树中所有节点的度的最大值。
上图中,树的度为 2。
节点的层次
从根节点开始,假设根节点为第 1 层,根节点的子节点为第 2 层,依此类推,如果某一个节点位于第 L 层,则其子节点位于第 L+1 层。
二叉树性质
性质1
在二叉树的第 i 层上至多有 2^(i-1)
个节点(i>=1)。
上图中:
- 第1层:1个:
2^(1-1)
=2^0
= 1; - 第2层:2个:
2^(2-1)
=2^1
= 2; - 第3层:4个:
2^(3-1)
=2^2
= 4。
性质2
深度为 k 的二叉树最多有 2^k-1
个节点(k>=1)。(这里注意是 2 的 k 次幂再减 1。)
- 如果有一层,最多1 = 2^1 - 1个节点
- 如果有两层,最多1+2 = 2^2 - 1个节点
- 如果有三层,最多1+2+4 = 2^3 - 1个节点
通过数据归纳法的论证,可以得出如果有 k 层,节点数最多为2^k-1
。
性质3
若在任意一棵二叉树中,有 n0
个叶子节点,有 n2
个度为 2 的节点,则必有 n0 = n2 + 1
如图:
- 度为 0 的节点是:C、D(2个)
- 度为 1 的节点是:B(1个)
- 度为 2 的节点是:A(1个)
- 2 = 1+1
性质4
具有 n 个节点的完全二叉树深为 (log2n)+1。
由满二叉树的定义我们可以知道,深度为 k 的满二叉树的节点数一定是(2^k)-1
,因为这是最多的节点个数。
那么对于 n = (2^k)-1
推到得到的满二叉树的深度为 k=log2(n+1)
。比如节点数为 15 的满二叉树,深度为 4。
证明:设完全二叉树的深度为 h,则根据性质 2 和完全二叉树的定义有:
它的节点数一定小于等于同样深度的满二叉树的节点数2^h - 1
,但一定多于2^(h-1)- 1
。
2^(h-1) - 1
< n <= 2^h - 1
或 2^(h-1)
<= n < 2^h
取对数 h-1 < log2n <= h,又 h 是整数, 因此有 h = (log2n)+1。
性质5
若对一棵有 n 个节点的完全二叉树进行顺序编号(1 ≤ i ≤ n),那么,对于编号为i(i≥1)的节点:
- 当 i=1 时,该节点为根,它无双亲节点 。
- 当 i>1 时,该节点的双亲节点的编号为 i/2 。
- 若 2i≤n,则有编号为 2i 的左节点,否则没有左节点 。
- 若 2i+1≤n,则有编号为 2i+1 的右节点,否则没有右节点 。
常见的三种遍历
二叉树遍历分为三种:前序、中序、后序,中序遍历最为重要。为啥叫这个名字?是根据根节点的顺序命名的。
先看一个简单的例子,如下是一个正常的满节点:
- 前序顺序是ABC(根节点排最先,然后同级先左后右);
- 中序顺序是BAC(先左后根最后右);
- 后序顺序是BCA(先左子树,后右子树,再根节点)。
代码实现二叉树遍历
使用递归的方式,实现下图二叉树的前序、中序、后序遍历。
首先创建一棵树:
public class Node {
private int data;
private Node leftNode;
private Node rightNode;
public Node(int data, Node leftNode, Node rightNode){
this.data = data;
this.leftNode = leftNode;
this.rightNode = rightNode;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Node getLeftNode() {
return leftNode;
}
public void setLeftNode(Node leftNode) {
this.leftNode = leftNode;
}
public Node getRightNode() {
return rightNode;
}
public void setRightNode(Node rightNode) {
this.rightNode = rightNode;
}
}
递归实现遍历:
public class BinaryTree {
/**
* 注意必须逆序建立,先建立子节点,再逆序往上建立,因为非叶子节点会使用到下面的节点,而初始化是按顺序初始化的,不逆序建立会报错
*
* @return
*/
public Node init() {
Node J = new Node(8, null, null);
Node H = new Node(4, null, null);
Node G = new Node(2, null, null);
Node F = new Node(7, null, J);
Node E = new Node(5, H, null);
Node D = new Node(1, null, G);
Node C = new Node(9, F, null);
Node B = new Node(3, D, E);
Node A = new Node(6, B, C);
//返回根节点
return A;
}
public void printNode(Node node) {
System.out.print(node.getData());
}
/**
* 先序遍历
*
* @param root
*/
public void theFirstTraversal(Node root) {
printNode(root);
if (root.getLeftNode() != null) {
//使用递归进行遍历左孩子
theFirstTraversal(root.getLeftNode());
}
if (root.getRightNode() != null) {
//递归遍历右孩子
theFirstTraversal(root.getRightNode());
}
}
/**
* 中序遍历
*
* @param root
*/
public void theInOrderTraversal(Node root) {
if (root.getLeftNode() != null) {
theInOrderTraversal(root.getLeftNode());
}
printNode(root);
if (root.getRightNode() != null) {
theInOrderTraversal(root.getRightNode());
}
}
/**
* 后序遍历
*
* @param root
*/
public void thePostOrderTraversal(Node root) {
if (root.getLeftNode() != null) {
thePostOrderTraversal(root.getLeftNode());
}
if (root.getRightNode() != null) {
thePostOrderTraversal(root.getRightNode());
}
printNode(root);
}
public static void main(String[] args) {
BinaryTree tree = new BinaryTree();
Node root = tree.init();
System.out.println("先序遍历");
tree.theFirstTraversal(root);
System.out.println("");
System.out.println("中序遍历");
tree.theInOrderTraversal(root);
System.out.println("");
System.out.println("后序遍历");
tree.thePostOrderTraversal(root);
System.out.println("");
}
}
打印结果:
先序遍历
631254978
中序遍历
123456789
后序遍历
214538796
通过上述的介绍,详细对于二叉树有了初步的认识。
如果你还想看更多优质原创文章,欢迎关注我的公众号「ShawnBlog」。