二叉树的概念
树有很多种,而二叉树就是每个节点的度最多为2的树。说通俗一点,就是每个节点最多最多只能有两个子节点的树。
二叉树的特点
- 每个节点最多有两棵子树,所以二叉树中不存在度大于2的节点。
- 子节点有左右之分,左子节点衍生的树是左子树,右子节点衍生的树叫右子树。
- 左子树和右子树是有顺序的,次序不能任意颠倒。
- 即使树中某个节点只有一颗子树,也要区分是左子树还是右子树。
二叉树具有5种基本形态
- 空二叉树
- 只有一个根节点
- 根节点只有左子树
- 根节点只有右子树
- 根节点只有左子树又有右子树
特殊二叉树
1.斜树
顾名思义,斜树就是斜的。所有的节点只有左子树的二叉树叫做左斜树,所有的节点只有右子树的二叉树叫做右子树,二者统称为斜树。
2.满二叉树
满的意思就是完美,如果这颗二叉树的所有节点都存在左子树和右子树,且所有的叶子节点都在同一层,那么这很完美,这样的二叉树被称为满二叉树。如图4就是一颗满二叉树。
满二叉树有以下特点:
- 叶子节点只能出现在最后一层。出现在其他层就不可能达到平衡。
- 非叶子节点的度一定为2,否则就缺胳膊少腿了。
- 在同样深度的二叉树中,满二叉树的节点最多,叶子最多。
3.完全二叉树
对一颗具有n个节点的二叉树按层序编号,如果编号为i(1<=i<=n)d的节点与同样深度的满二叉树中编号为i的节点在二叉树中位置完全相同,则这棵树被称为满二叉树。如图5就是一颗完全二叉树,图6和图7都不是完全二叉树。
小窍门:从上至下、从左到右,按顺序给节点编号,如果编号出现空档,这就不是完全二叉树。
完全二叉树的特点:
- 叶子节点只能出现在最后两层。
- 最下层的叶子节点一定集中在左部连续位置。
- 倒数第二层的叶子节点一定集中在右部连续位置。
- 若存在度为1的节点,则该节点一定只有左孩子。
- 同样节点数的二叉树,完全二叉树的深度最小。
二叉树的性质
性质1:在二叉树的第i层,最多有2i-1个节点(i>=1)
性质2:深度为k的二叉树最多有2k-1个节点(k>=1)
性质3:对于任意一颗二叉树T,如果叶子节点的数量为n0,度为1的节点的数量为n1,度为2的节点的数量为n2,节点总数为n,那么n=n0+n1+n2,n0 = n2+1
性质4:具有n个节点的完全二叉树的深度为log2n+1,其中log2n向下取整。
性质5:如果对一颗有n个节点为完全二叉树的节点从上到下、从左到右编号,对任一节点i右:
- 如果i=1,则i是二叉树的根,无双亲;如果i>1,则其双亲是i/2(向下取整)。
- 如果2i>n,则节点i无左孩子,节点i为叶子节点,否则其做孩子是节点2i。
- 如果2i+1>n,则节点i无右孩子,否则其有孩子是节点2i+1。
二叉树的遍历
二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中的所有节点,使得每个节点被访问且只被访问一次。
前序遍历(根左右)
规则:若二叉树为空,则返回空操作;否则访问根节点,然后前序遍历左子树,再前序遍历右子树。
如图9,前序遍历的结果是ABDGHCEIF
中序遍历(左根右)
规则:若二叉树为空,则返回空操作;否则从跟接单开始(注意并不是先访问根节点),中序遍历左子树,然后访问根节点,最后中序遍历右子树。
如图10,中序遍历的结果是GDHBAEICF
后序遍历(左右根)
规则:若二叉树为空,则返回空操作;否则从根节点开始(注意也不是先访问根节点),后序遍历左子树,然后后序遍历右子树,最后访问根节点。
如图11,后序遍历的结果是GHDBIEFCA
二叉树的存储结构
顺序存储结构
顺序存储结构就是用一维数组存储二叉树中的节点,节点在数组中的下标要能体现出节点之间的逻辑关系,比如双亲与孩子的关系。
对于一般的二叉树。尽管层序编号不能反映逻辑关系,但是可以将其按完全二叉树编号,只不过把不存在的节点设置为“^”,如图14所示。
考虑一种极端情况,一棵深度为k的右斜树,他只有k个节点,却要分配2k-1个节点,这明显就是浪费空间,如图15所示,所以顺序存储的结构一般只用于完全二叉树,堆排序就用到了完全二叉树的顺序存储。
二叉链表
既然顺序存储结构适用性不强,我们就要考虑链式存储结构,二叉树每个节点最多有两个孩子,所以为他设计一个数据域和两个指针域是比较自然的想法,像这种链表叫做二叉链表。链表的节点如图16所示,图17是一颗用二叉链表组织的二叉树。
代码实现
/**
* 定义二叉树,使用二叉链表存储结构
*
* @author chenzhiyuan
* @date 2019-10-02 22:52
*/
public class BiTree {
private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 二叉树的根节点
private BiTNode root = null;
public static void main(String[] args) throws IOException {
BiTree tree = new BiTree();
tree.buildBiTree();
System.out.println("前序遍历二叉树:");
tree.preOrderTraverse(tree.root);
System.out.println("中序遍历二叉树:");
tree.inOrderTraverse(tree.root);
System.out.println("后序遍历二叉树:");
tree.postOrderTraverse(tree.root);
}
public void buildBiTree() throws IOException {
root = buildBiTree(root);
}
/**
* 递归构建二叉树,先输入根节点,然后递归侯建左子树,最后递归构建右子树。
* 如果输入为#,标识当前节点为null
*
* @param node 被构建的树的根节点,可以为null
* @return 被构建的树的根节点
*/
public BiTNode buildBiTree(BiTNode node) throws IOException {
String str = br.readLine();
if ("#".equals(str)) {
return null;
} else {
if (node == null) {
node = new BiTNode();
}
node.data = str;
}
// 构建左子树
node.lChild = buildBiTree(node.lChild);
// 构建右子树
node.rChild = buildBiTree(node.rChild);
// 返回当前被构建的树的根节点
return node;
}
/**
* 前序遍历
*
* @param node
*/
public void preOrderTraverse(BiTNode node) {
if (node == null) {
return;
}
System.out.println(node.data);
// 遍历左子树
preOrderTraverse(node.lChild);
// 遍历右子树
preOrderTraverse(node.rChild);
}
/**
* 中序遍历
*
* @param node
*/
public void inOrderTraverse(BiTNode node) {
if (node == null) {
return;
}
inOrderTraverse(node.lChild);
System.out.println(node.data);
inOrderTraverse(node.rChild);
}
/**
* 后序遍历
*
* @param node
*/
public void postOrderTraverse(BiTNode node) {
if (node == null) {
return;
}
postOrderTraverse(node.lChild);
postOrderTraverse(node.rChild);
System.out.println(node.data);
}
/**
* 二叉链表的节点定义
*/
private class BiTNode {
private BiTNode lChild;
private String data;
private BiTNode rChild;
@Override
public String toString() {
return "BiTNode{" +
"data='" + data + '\'' +
'}';
}
}
}
测试:使用如12所示的二叉树
A
B
D
H
#
#
I
#
#
E
J
#
#
#
C
F
#
#
G
#
#
前序遍历二叉树:
A
B
D
H
I
E
J
C
F
G
中序遍历二叉树:
H
D
I
B
J
E
A
F
C
G
后序遍历二叉树:
H
I
D
J
E
B
F
G
C
A