第四章 树
一 、树的基本概念
树(Tree)是n(n>=0)个结点的有限集。
在任意一棵非空树中:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,…,Tn,其中每个集合本身又是一棵树,并称为根的子树(SubTree)
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/c460138a08d8565f37f410c89cf184c4.png)
树的结点包含一个数据元素及若干指向其子树的分支。
1.1 度
结点拥有的子树树称为结点的度(Degree)如:上图D的度为3,E的度为1,G的度为0。
度为0的结点称为叶子(Leaf)或终端结点,如:上图G、H、I、J、F都是树的叶子
度不为0的结点称为非终端结点或分支结点,如:上图A、B、C、D、E
树的度是树内各节点的度的最大值,如:上图的树的度为 3 。
1.2 结点(家谱图)
结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲(Parent)如:上图C是A的孩子,A是C的双亲。
同一个双亲的孩子叫兄弟(Sibling)如:上图G、H、I为互为兄弟
其双亲在同一层的结点互为堂兄弟。如上图D与E、F互为堂兄弟
结点的祖先是从根到该结点所经分支上的所有结点。如:上图H的祖先为A、B、D
1.3 层次,深度(你家几代同堂啊?)
结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层,
树中结点的最大层次成为树的深度(Depth)或高度。如:上图树的深度为4
1.4 有序树与无序树(长子,次子。。)
树中结点的各子树看成从左至右是有次序的(即不能互换),则称该树为有序树,否则成为无序树。
有序树中最左的子树的根称为第一个孩子,最右边的称为最后一个孩子。(毕竟有序,排好了谁是老大,谁是老二,长兄有序嘛)
森林(Forest)是m(m>=0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。
由此也可以森林和树相互递归的定义来描述树。
二、二叉树
2.1 基本概念
二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:
(1) 空二叉树——如图(a);
(2) 只有一个根结点的二叉树——如图(b);
(3) 只有左子树——如图(c);
(4) 只有右子树——如图(d);
(5) 完全二叉树——如图(e)。
注意:尽管二叉树与树有许多相似之处,但二叉树不是树的特殊情形。
2.2 类型
(1) 完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树,如下图。
(2) 满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
(3) 平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/5c6802bd7778fd7746dae3821522e7ac.png)
2.3 二叉树的性质
性质1:在二叉树的第i层上至多有
2i−1
2
i
−
1
个结点(i>=1)。
性质2:深度为k的二叉树至多有
2k−1
2
k
−
1
个结点(k>=1)。
性质3:对任何一颗二叉树T,如果其终端结点数为
n0
n
0
,度为2的结点 数为
n2
n
2
,则
n0
n
0
=
n2
n
2
+1.
性质4:具有n个结点的完全二叉树深度为[
log2n
l
o
g
2
n
]+1 ([x]表示不大于x的最大整数)。
性质5:如果对一颗有n个结点的完全二叉树(其深度为[
log2n
l
o
g
2
n
]+1) 的结点按层序编号(从第1层到第[
log2n
l
o
g
2
n
]+1层,每层从左到 右),对任意一个结点i(1<=i<=n)有:
1). 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]
2). 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩 子是结点2i。
3). 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
性质3证明:
设n为总结点数,
n1
n
1
为度为1的结点数,
n2
n
2
为度为2的结点数,
n0
n
0
为终端结点数
用代数表达就是分支线总数=
n
n
- 1 = + 2
n2
n
2
因为我们有等式:
n
n
= +
n1
n
1
+
n2
n
2
所以可推导出:
n0
n
0
+
n1
n
1
+
n2
n
2
- 1 =
n1
n
1
+ 2
n2
n
2
,结论就是:
n0
n
0
=
n2
n
2
+1.
2.4 二叉树的顺序存储结构
如上图,一般二叉树中相对于完全二叉树为空的节点在存储结构中用^代替
2.5 二叉链表
2.6 二叉树的遍历
准备工作:
public class BinaryTree {
private TreeNode root = null;
public BinaryTree(){
root = new TreeNode(1, "A");
}
/**
* 构建二叉树
* A
* B C
* D E F
*/
public void createBinaryTree(){
TreeNode nodeB = new TreeNode(2, "B");
TreeNode nodeC = new TreeNode(3, "C");
TreeNode nodeD = new TreeNode(4, "D");
TreeNode nodeE = new TreeNode(5, "E");
TreeNode nodeF = new TreeNode(6, "F");
root.leftChild = nodeB;
root.rightChild = nodeC;
nodeB.leftChild = nodeD;
nodeB.rightChild = nodeE;
nodeC.rightChild = nodeF;
}
/**
* 求二叉树的高度
* @author Administrator
*
*/
public int getHeight(){
return getHeight(root);
}
private int getHeight(TreeNode node) {
if(node == null){
return 0;
}else{
int i = getHeight(node.leftChild);
int j = getHeight(node.rightChild);
return (i<j)?j+1:i+1;
}
}
/**
* 获取二叉树的结点数
* @author Administrator
*
*/
public int getSize(){
return getSize(root);
}
private int getSize(TreeNode node) {
if(node == null){
return 0;
}else{
return 1+getSize(node.leftChild)+getSize(node.rightChild);
}
}
public class TreeNode{
private int index;
private String data;
private TreeNode leftChild;
private TreeNode rightChild;
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public TreeNode(int index,String data){
this.index = index;
this.data = data;
this.leftChild = null;
this.rightChild = null;
}
}
}
方式:
1、前序遍历
规则是若二叉树为空,则空操作返回,否则先访问跟结点,然后前序遍历左子树,再前序遍历右子树
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/cedddae4799b3be80b45918ab613dee8.png)
/**
* 前序遍历——迭代
* @author Administrator
*
*/
public void preOrder(TreeNode node){
if(node == null){
return;
}else{
System.out.println("preOrder data:"+node.getData());
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
/**
* 前序遍历——非迭代
*/
public void nonRecOrder(TreeNode node){
if(node == null){
return;
}
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(node);
while(!stack.isEmpty()){
//出栈和进栈
TreeNode n = stack.pop();//弹出根结点
//压入子结点
System.out.println("nonRecOrder data"+n.getData());
if(n.rightChild!=null){
stack.push(n.rightChild);
}
if(n.leftChild!=null){
stack.push(n.leftChild);
}
}
}
2.中序遍历
规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/ac11a27293ed42b23dc3349bb7d7a95a.png)
/**
* 中序遍历——迭代
* @author Administrator
*
*/
public void midOrder(TreeNode node){
if(node == null){
return;
}else{
midOrder(node.leftChild);
System.out.println("midOrder data:"+node.getData());
midOrder(node.rightChild);
}
}
3.后序遍历
规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/e6b0f8b8a5bab988c686644b0cf2bbb0.png)
/**
* 后序遍历——迭代
* @author Administrator
*
*/
public void postOrder(TreeNode node){
if(node == null){
return;
}else{
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.println("postOrder data:"+node.getData());
}
}
4.层序遍历
规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层,按从左到右的顺序对结点逐个访问
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/5cc6a0ee88083d35fb887331ee3fd264.png)