简介
树是数据元素之间具有层次关系的非线性结构;
与线性结构不同,树中的数据元素具有一对多的逻辑关系;
树的术语
- 结点:树的结点就是构成树的数据元素;
- 结点的路径:指从根结点到该结点所经过结点的顺序排列;
- 路径的长度:指的是路径中包含的分支数;
- 结点的度:指的是结点拥有的子树的数目;
- 树的度:树中所有结点的度的最大值;
- 叶结点:树中度为0的结点,也叫终端结点;
- 分支结点:树中度不为0的结点,也叫非终端结点;
- 子结点:结点的子树的根结点,也叫孩子结点;
- 父结点:具有子结点的结点叫该子结点的父结点,也叫双亲结点;
- 子孙结点:结点的子树中的任意结点;
- 祖先结点:结点的路径中除自身之外的所有结点;
- 兄弟结点:和结点具有同一父结点的结点;
- 结点的层次:树中根结点的层次为0,其他结点的层次是父结点的层次加1;
- 树的深度:树中所有结点的层次数的最大值加1;
- 有序树:树的各结点的所有子树具有次序关系,不可以改变位置;
- 无序树:树的各结点的所有子树之间无次序关系,可以改变位置;
- 森林:森林是由多个互不相交的树构成的集合。给森林加上一个根结点就变成一颗树,将树的根结点删除就变成森林;
二叉树
1:普通二叉树
二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree);
2:满二叉树
除最后一层外,每一层上的所有节点有两个子节点。也就是说,倒数第二层的每个节点都有两个子节点,那么最后一层的节点数一定是倒数第二层的两倍,所以最后一层一个节点都不能少;
完全二叉树
在最后一层的节点是可以缺少的,其节点数可能是倒数第二层节点数的2倍,也可能是1个、2个。只不过,这些缺的节点只能是最右边的;
二叉树的性质
性质一到4:
- 二叉树中第 i 层的结点数最多为 2^i ;
- 深度为 h 的二叉树最多有 2^h-1 个结点;
- 若二叉树的叶结点的个数为 n,度为 2 的结点个数为 m,有 n=m+1;
- 具有 n 个结点的完全二叉树,其深度为 [㏒₂n]+1 或者 [㏒₂(n+1)];
性质五:具有 n 个结点的完全二叉树,从根结点开始自上而下、从左向右对结点从 0 开始编号。对于任意一个编号为 i 的结点:
- 若 i =0 ,结点为根结点,没有父结点;若 i >0 ,则父结点的编号为 [(i-1)/2];
- 若 2i+1>=n,该结点无左孩子,否则左孩子结点的编号为 2i+1;
- 若 2i+2>=n,该结点无右孩子,否则右孩子结点的编号为 2i+2;
二叉树的存储结构
1:二叉树的顺序存储结构
二叉树的顺序存储结构是指将二叉树的各个结点存放在一组地址连续的存储单元中,所有结点按结点序号进行顺序排序。
为了存储非完全二叉树,需要在树中添加虚结点使其成为完全二叉树后再进行存储,这样会造成存储空间的浪费;
2:二叉树的链式存储结构
二叉树的链式存储结构是指将二叉树的各个结点随机存放再存储空间中,二叉树的各结点间的逻辑关系由指针确定。每个结点至少要有两条链分别连接左、右孩子结点才能表达二叉树的层次关系;
根据指针域个数的不同,二叉树的链式存储结构又分为以下两种:
- 二叉链式存储结构:二叉树的每个结点设置两个指针域和一个数据域(最常用);
- 三叉链式存储结构:二叉树的每个结点设置3各指针域和一个数据域;
3:二叉链式存储结构的结点类的描述
public class BiTreeNode {
public Object data;//存放结点的数据值
public BiTreeNode lchild,rchild;//存放结点的左右孩子地址
public BiTreeNode(){
this(null,null,null);
}
public BiTreeNode(Object data){
this(data,null,null);
}
public BiTreeNode(Object data,BiTreeNode lchild,BiTreeNode rchild){
this.data=data;
this.lchild=lchild;
this.rchild=rchild;
}
}
4:二叉树类的描述
public class BiTree {
private BiTreeNode root;//二叉树的根结点
public BiTree(){
root=null;
}
public BiTree(BiTreeNode root){
this.root=root;
}
}
二叉树的遍历
- 层次遍历:自上而下、从左到右依次访问每层的结点;
- 先序遍历:先访问根结点,再先序遍历左子树,最后先序遍历右子树;
- 中序遍历:先中序遍历左子树,再访问根结点,最后中序遍历右子树;
- 后序遍历:先后序遍历左子树,再后序遍历右子树,最后访问根结点;
二叉树遍历操作实现的递归算法
1:先序遍历
public void preOrder(BiTreeNode root){
System.out.print(root.data+" ");//访问根结点
preOrder(root.lchild);//先序遍历左子树
preOrder(root.rchild);//先序遍历右子树
}
2:中序遍历
public void inOrder(BiTreeNode root){
inOrder(root.lchild);//先序遍历左子树
System.out.print(root.data+" ");//访问根结点
inOrder(root.rchild);//先序遍历右子树
}
3:后序遍历
public void postOrder(BiTreeNode root){
preOrder(root.lchild);//先序遍历左子树
preOrder(root.rchild);//先序遍历右子树
System.out.print(root.data+" ");//访问根结点
}
二叉树的建立
由中序和先序遍历序列建立二叉树
步骤:
- 取先序遍历序列的第一个结点作为根结点,序列的结点个数为 n;
- 在中序遍历序列中寻找根结点,其位置为 i ;
- 先序遍历序列中根结点之前的 i 各结点构成的序列为根结点的左子树先序遍历序列。先序遍历序列之后的 n-i-1个结点构成的序列为根结点的右子树先序遍历序列
- 对左、右子树重复步骤1、2、3,确定左、右子树的根结点和子树的左、右子树;
- 算法递归进行即可建立一颗二叉树;
public BiTree(String preOrder,String inOrder,int pre,int in,int n){
if(n>0){
char c=preOrder.charAt(pre);//c为先序序列的根结点
int i=0;
for(;i<n;i++){//i为根结点在中序遍历序列中的位置
if(inOrder.charAt(i+in)==c){
break;
}
}
root=new BiTreeNode(c);
root.lchild=new BiTree(preOrder,inOrder,pre+1,in,i).root;//递归寻找左子树的根结点
root.rchild=new BiTree(preOrder,inOrder,pre+i+1,in+i+1,n-i-1).root; //递归寻找右子树的根结点
}
}
哈夫曼树的构造
哈夫曼编码
哈夫曼编码是一种变长的编码方案,数据的编码因其使用的频率的不同而长短不一,使用频率高的数据其编码较短,使用频率低的数据其编码较长,从而使所有数据的编码总长度最短。各数据的使用频率通过在全部数据中统计重复数据的出现的次数获得;
哈夫曼编码的译码过程是构造过程的逆过程,从哈夫曼树的根结点开始对编码的每一位进行判别,如果为 0 进入左子树,如果为 1 进入右子树,直到到达叶结点,即译出了一个字符;
哈夫曼树和哈夫曼编码的类的描述
结点类:
public class HuffmanNode {
public int weight;//结点的权值
public int flag;//标记是否已加入哈夫曼树
public HuffmanNode parent,lchild,rchild;//父结点和孩子结点
public HuffmanNode(){
weight=0;
flag=0;
parent=lchild=rchild=null;
}
public HuffmanNode(int weight){
this.weight=weight;
flag=0;
parent=lchild=rchild=null;
}
}
构造哈夫曼树:
public class HuffmanTree {
public HuffmanTree(int[] w){
int l=w.length;//字符的个数
int n=2*l-1;//哈夫曼树的结点数
HuffmanNode []node=new HuffmanNode[n];
for(int i=0;i<l;i++){//构造权值为w[i]l个叶结点
node[i]=new HuffmanNode(w[i]);
}
for(int i=n;i>n;i++){//构造哈夫曼树
HuffmanNode m1=selectMin(node,i-1);
m1.flag=1;
HuffmanNode m2=selectMin(node,i-1);
m2.flag=1;
node[i].lchild=m1;
node[i].rchild=m2;
node[i].weight=m1.weight+m2.weight;
m1.parent=node[i];
m2.parent=node[i];
}
}
public HuffmanNode selectMin(HuffmanNode[] node,int i){//寻找不在树中的权值最小的点
HuffmanNode min=node[i];
for(int j=0;j<=i;j++){
if(node[j].weight<min.weight&&node[j].flag==0)
min=node[j];
}
return min;
}
}
求哈夫曼编码:
public int[][] HuffmanCode(HuffmanNode[] node,int n){
int [][] HuffmanCode=new int[n][n];//存储哈夫曼编码
for(int i=0;i<n;i++){//构造n个字符的哈夫曼编码
int k=n-1;
HuffmanNode t=node[i],p=t.parent;
for(;p!=null;p=p.parent){
if(p.lchild==t)
HuffmanCode[i][k--]=0;
else
HuffmanCode[i][k--]=1;
}
}
return HuffmanCode;
}