本文中图片均来自数据与结构算法电子书截图
1、树的基本术语
- ①节点:包含一个数据元素及若干指向子树分支的信息 。
- ②节点的度:一个节点拥有子树的数目称为节点的度 。
- ③叶子节点:也称为终端节点,没有子树的节点或者度为零的节点 。
- ④分支节点:也称为非终端节点,度不为零的节点称为非终端节点 。
- ⑤树的度:树中所有节点的度的最大值 。
- ⑥节点的层次:从根节点开始,假设根节点为第1层,根节点的子节点为第2层,依此类推,如果某一个节点位于第L层,则其子节点位于第L+1层。
- ⑦树的深度:也称为树的高度,树中所有节点的层次最大值称为树的深度 。
- ⑧有序树:如果树中各棵子树的次序是有先后次序,则称该树为有序树 。
- ⑨无序树:如果树中各棵子树的次序没有先后次序,则称该树为无序树 。
- ⑩森林:由m(m≥0)棵互不相交的树构成一片森林。如果把一棵非空的树的根节点删除,则该树就变成了一片森林,森林中的树由原来根节点的各棵子树构成
2、二叉树
二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。
- 满二叉树,深度为k且有
2
k
−
1
2^k - 1
2k−1个结点的二叉树称为满二叉树
- 完全二叉树,若一棵二叉树至多只有最下面的两层结点的度数可以小于 2,并且最下一层上的结点都集中在该层最左边的若干位置上,则此二叉树称为完全二叉树。
由定义及示例可以看出满二叉树是完全二叉树,但完全二叉树不一定是满二叉树。在满二叉树的最下一层上从最右边开始连续删去若干结点后得到的二叉树仍然是一棵二叉树。因此,在完全二叉树中,若某个结点没有左儿子,则它一定没有右儿子,即该结点必是叶结点。
2.1、二叉树的性质
- 性质1:二叉树第i(i ≥ 1)层上的节点数最多为 2 i − 1 2^{i-1} 2i−1。
- 性质2:高度为k的二叉树最多有 2 k − 1 2^{k-1} 2k−1个节点。
- 性质3:若在任意一棵二叉树中,有 n 0 n_0 n0个叶子节点,有 n 2 n_2 n2个度为2的节点,则必有 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1。
- 性质4:具有n个结点的完全二叉树的高度为 l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)。
- 性质5:满二叉树原理,非空满二叉树的叶节点数等于其分支节点加1。
- 性质6:一棵非空二叉树空子树的数目等于其结点数目加1。
2.2、二叉树的存储结构
2.2.1、二叉树的顺序存储结构
二叉树的顺序存储结构是把二叉树的所有结点按照一定的顺序存储到一组包含n个存储单元的空间中。
二叉树顺序存储的原则:不管给定的二叉树是不是完全二叉树,都看作完全二叉树,按完全二叉树的层次顺序(从上到下,从左到右)把各个结点依次存入数组中,如下图所示
在顺序存储结构中,由某结点的存储单元可以推出其父结点、左右儿子及兄弟的地址,假定给定的结点的地址为
I
(
I
≥
1
)
I(I≥1)
I(I≥1),则:
- 若 I = 1 I=1 I=1,则该结点为根结点,无父亲。
- 若 I ≠ 1 I≠1 I=1,则该结点的父结点地址为 I / 2 I/2 I/2的整数部分。
- 若 2 I ≤ n 2I≤n 2I≤n,则该结点的左儿子结点地址为 2 I 2I 2I,否则该结点无左儿子。
- 若 2 I + 1 ≤ n 2I + 1≤n 2I+1≤n,则该结点的右儿子结点地址为 2 I + 1 2I + 1 2I+1,否则该结点无右儿子。
- 若 I I I为奇数(不为1),则该结点的左兄弟结点地址为 I − 1 I - 1 I−1。
- 若 I I I为偶数(不为n),则该结点的右兄弟结点地址为 I + 1 I + 1 I+1。
顺序存储结构的优缺点:
优点:存储完美二叉树既简单又节省空间。
缺点:对于高度为k的非完美二叉树,需要
2
k
−
1
2^k - 1
2k−1的空间来存储结点,随着二叉树高度的增大,存储的空接点会急速增多,浪费大量空间。
2.2.2、二叉树的链式存储结构
二叉树的链式存储结构中每个结点由数据域和指针域两部分组成。二叉树每个结点的指针域由两个,一个指向左儿子,一个指向右儿子。二叉树的链式存储结构也称二叉链表。
2.3、二叉树的遍历
- 前序遍历(NLR):根节点->左子节点->右子节点
- 中序遍历(LNR):左子节点->根节点->右子节点
- 后序遍历(LRN):左子节点->右子节点->根节点
- 层次遍历:按层次从0层开始,每次从左到右依次访问
示例中的二叉树:
代码示例:
public class Test16 {
public static void main(String[] args) {
TreeNode root = new TreeNode('A');
TreeNode B = new TreeNode('B');
TreeNode C = new TreeNode('C');
TreeNode D = new TreeNode('D');
TreeNode E = new TreeNode('E');
TreeNode F = new TreeNode('F');
TreeNode G = new TreeNode('G');
root.left = B;
root.right = C;
B.left = D;
B.right = E;
C.left = F;
C.right = G;
root.preOrder(root);
System.out.println();
root.middleOrder(root);
System.out.println();
root.afterOrder(root);
System.out.println();
root.levelOrder(root);
}
}
//链式存储孩子表示法
class TreeNode {
public TreeNode left;
public char value;
public TreeNode right;
public TreeNode(char value) {
this.value = value;
}
public TreeNode(TreeNode left, char value, TreeNode right) {
this.left = left;
this.value = value;
this.right = right;
}
//前序遍历
public void preOrder(TreeNode root) {
if (root == null) return;
System.out.print(root.value + " ");
preOrder(root.left);
preOrder(root.right);
}
//中序遍历
public void middleOrder(TreeNode root) {
if (root == null) return;
middleOrder(root.left);
System.out.print(root.value + " ");
middleOrder(root.right);
}
//后序遍历
public void afterOrder(TreeNode root) {
if (root == null) return;
afterOrder(root.left);
afterOrder(root.right);
System.out.print(root.value + " ");
}
//层次遍历
public void levelOrder(TreeNode root) {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
System.out.print(cur.value + " ");
if (cur.left != null) queue.offer(cur.left);
if (cur.right != null) queue.offer(cur.right);
}
}
}