什么是二叉树?
二叉树
又称knuth树
,是一个由有限节点所组成的集合,此集合可以为空集合,或由一个树根及左右两个子树所组成。
为什么使用二叉树?
树状结构在计算机内存中的存储方式往往以链表为主,对于n元树来说,因为每个节点的分支度都不同,所以为了方便起见,一般就取n为链接个数的最大固定长度,那么每个节点中就包含了一份数据与n个链接。
假设n元树有m个节点,那么这颗树应共享了m*n个链接字段。除了树根之外,每个非空链接都指向一个节点,所以对于m个节点的树,应有m-1个实际链接,则树的空链接个数为mn-(m-1)=m(n-1)+1
,则n元树的链接浪费率为
m
(
n
−
1
)
+
1
m
n
\frac{m(n-1)+1}{mn}
mnm(n−1)+1。根据这个式子我们可以发现,当n=2时,树的链接浪费率最低为
1
2
\frac{1}{2}
21,因此我们常常使用二叉树结构。
二叉树的节点数
如果一颗二叉树的深度为k,那么它的总结点数应为:
∑ i = 1 k 2 i − 1 = 2 0 + 2 1 + . . . + 2 k − 1 = 2 k − 1 2 − 1 = 2 k − 1 \sum_{i=1}^k 2^{i-1}=2^0+2^1+...+2^{k-1}=\frac{2^k-1}{2-1}=2^k-1 i=1∑k2i−1=20+21+...+2k−1=2−12k−1=2k−1
二叉树的存储方式
树状结构在计算机中的存储方式往往以链表为主,这是因为链表的指针用来处理数比较方便,只需改变指针即可。不过也可以使用数组来存储二叉树,两种方法其实都各有利弊。
数组表示法
如果要使用数组来存储二叉树,首先要将二叉树想象成一个满二叉树,,然后依序存放在一维数组中。
比如下面这颗树:
树中的A,B,C,D
代表节点的值,其余的为空节点,它们的位置如图所示,那么如果让它们放进数组,应该是这样的:
索引 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
值 | A | B | C | D |
建立二叉树需要遵守小于父节点的值放在左子节点,大于父节点的值放在右子节点
的规则,因此左子树的值一定全部小于树根,右子树的值一定大于树根。
下面是一个使用数组存储二叉树的示例:
public class arrayBinaryTree {
public static void main(String[] args) {
int i,level;
int[] data = {6,3,5,9,7,8,4,2}; // 原始数据
int btree[] = new int[16]; // 二叉树数组大小,长度16是因为使用后15个空间,让索引从1开始,比较好理解
for (i=0; i<16; i++)
btree[i] = 0;
System.out.println("原始数组内容:");
for (i=0;i<8;i++)
System.out.print("["+data[i]+"]");
System.out.println();
// 将原始数据添加入二叉树
for (i=0;i<8;i++) {
for (level=1;btree[level]!=0;) {
if (data[i]>btree[level])
level = level*2+1;
else
level = level*2;
}
btree[level] = data[i];
}
System.out.println("二叉树:");
for (i=1;i<16;i++)
System.out.print("["+btree[i]+"]");
}
}
OUT:
原始数组内容:
[6][3][5][9][7][8][4][2]
二叉树:
[6][3][9][2][5][7][0][0][0][4][0][0][8][0][0]
链表表示法
使用链表表示法来存储二叉树可以定义TreeNode
和BinaryTree
类,其中前者代表二叉树中的一个节点,后者就代表一棵二叉树。
节点中应包括存放的数据、左子节点的指针和右子节点的指针
。二叉树中应该实现一个方法即将数据插入到二叉树中。
示例如下:
/*
TreeNode 二叉树节点
*/
class TreeNode {
int value;
TreeNode leftNode,rightNode;
public TreeNode(int value) {
this.value = value;
leftNode = null;
rightNode = null;
}
}
/*
BinaryTree 二叉树
*/
class BinaryTree {
public TreeNode rootNode;
public BinaryTree(int[] data) {
for (int i=0;i<data.length;i++)
Add_Node_To_BinaryTree(data[i]);
}
/*
将节点加入二叉树中
*/
void Add_Node_To_BinaryTree(int value) {
TreeNode currentNode = rootNode;
// 如果根节点为空,先创建根节点
if (rootNode == null) {
rootNode = new TreeNode(value);
return;
}
// 循环寻找适合当前节点的位置
while (true) {
if (value<currentNode.value) {
if (currentNode.leftNode == null) {
currentNode.leftNode = new TreeNode(value);
return;
}
else currentNode = currentNode.leftNode;
}else {
if (currentNode.rightNode == null) {
currentNode.rightNode = new TreeNode(value);
return;
}else currentNode = currentNode.rightNode;
}
}
}
// 打印各个节点值(中序遍历,即结果从小到大排序)
void printTree(TreeNode node) {
if (node != null) {
printTree(node.leftNode);
System.out.print("["+node.value+"]");
printTree(node.rightNode);
}
}
}
public class LinkBinaryTree {
public static void main(String[] args) {
int[] content = {11, 13, 15, 17, 19, 28, 26, 24, 22, 20};
BinaryTree binarytree = new BinaryTree(content);
binarytree.printTree(binarytree.rootNode);
}
}
OUT:
[11][13][15][17][19][20][22][24][26][28]
如上使用链表来建立了二叉树,并根据中序遍历
的方法打印出了二叉树存储的数据,可以看到数据是从小到大排列的。
二叉树遍历
二叉树的遍历就是将树中的每一个节点各访问一次,然后在遍历完毕后将树中的数据转化为线性关系。
如下一个二叉树节点:
如果想将它遍历,则共有六种遍历方法:ABC,ACB,BAC,BCA,CAB,CBA
,不过既然二叉树满足左子节点值小于父节点而右子节点值大于父节点
关系,那么从左向右遍历和从右向左遍历得到的结果是类似的,只要将结果反转一下就一样了,因此我们可以规定遍历规则为一律从左向右
,那么就剩下三种遍历方式了:BAC,ABC,BCA
。
这三种遍历方式就是
中序遍历(BAC)
:左子树->树根->右子树前序遍历(ABC)
:树根->左子树->右子树后序遍历(BCA)
:左子树->右子树->树根
其实这三种遍历方式,你只要记住树根的遍历顺序就行了,树根在前就是前序遍历,树根在后就是后序遍历,而左子树一定在右子树之前遍历的。
如下一颗子树:
它的前中后序遍历结果应该是:
ABDECF
DBEACF
DEBFCA
这很简单,大家应该一看就知道如何遍历了。
那么如果我们想要将一棵二叉树的各个节点值遍历出来,在程序中应该怎么实现呢?在链表表示法中我们已经给出了中序遍历的方法,方法中巧妙的运用了递归的方式进行遍历。下面给出前中后序遍历的代码:
// 前序遍历
void PreprintTree(TreeNode node) {
if (node != null) {
System.out.print("["+node.value+"]");
PreprintTree(node.leftNode);
PreprintTree(node.rightNode);
}
}
// 中序遍历
void MidprintTree(TreeNode node) {
if (node != null) {
MidprintTree(node.leftNode);
System.out.print("["+node.value+"]");
MidprintTree(node.rightNode);
}
}
// 后序遍历
void PosprintTree(TreeNode node) {
if (node != null) {
PosprintTree(node.leftNode);
PosprintTree(node.rightNode);
System.out.print("["+node.value+"]");
}
}