(一)、树
1、什么是树?
在编程中,树表示的是一种由N个节点的有限集合而构成的树形存储结构,它有且仅有一个"根"节点(没有父节点),其余节点形成的树(互不相交)称为"根"节点的子树
2、树的结构图
如上图所示,A是这棵树的"根"节点,A节点有3个子树,如下:
B节点的子树是E,C节点的子树是F,D节点的子树是G和H
3、树型结构的好处
将数据形成层次关系管理,效率较高
4、树的基本术语
节点的度: 节点的子树个数
树的度: 树中所有节点中最大的度
叶节点: 度为0的节点
父节点: 有子树的节点,这个节点称为它所形成子树的父节点
子节点: 父节点中的子树与之(父节点)直接关联的节点
树的高度: 树的最大层数
如下图所示,A节点的度为3,B节点的度为2,C节点的度为1,…;这棵树的度为3(所有节点中最大的度);这棵树中的叶节点有:E、K、G、L、I、J;B节点的父节点是A,C节点的父节点是B,D节点的父节点是A,E节点的父节点是B,…;A节点的子节点有:B、C、D,B节点的子节点有:E、F,…;树的高度为4
5、树与非数的判断
1、子树是不相交的
2、除"根"节点外,没有节点有且仅有一个父节点
3、N个节点形成的数只有N-1条边
根据上面3个判断条件,下面哪些是非树,哪些是树?
非树: 图一、图二、图三
树: 图四
(二)、二叉树
1、普通二叉树的定义
度为2的树(树中所有结点中的最大节点为2)
2、普通二叉树的结构图
3、完全二叉树的定义
除叶节点外,所有节点的度为2的树称为完全二叉树
4、完全二叉树的结构图
5、二叉排序树(二叉查找树或二叉搜索树)的定义
在一颗二叉树中,左子树的所有值小于根节点的值,右子树的所有值大于根节点的值,并且在左、右子树中,左节点的值要小于父节点的值,右节点的值要大于父节点的值
6、二叉排序树的结构图
7、二叉树的遍历
(1)前序遍历: 首先访问根节点,然后遍历左子树,最后遍历右子树(根-左-右)
(2)中序遍历: 首先遍历左子树,然后访问根节点,最后遍历右子树(左-根-右)
(3)后序遍历: 首先遍历左子树,然后遍历右子树,最后访问根节点(左-右-根)
例:如下图,它的前序遍历:FCADBEHGM
中序遍历:ACBDFHEMG
后序遍历:ABDCHMGEF
8、二叉树(上图)的代码实现并遍历
binaryTree.java
/**
* 二叉树
*/
public class binaryTree {
static class Node{
private Object data; //数据
private Node parent; //父节点
private Node left; //左节点
private Node right; //右节点
}
private static Node creatTree(){
//建立根节点F
Node root = new Node();
root.data = 'F';
//建立根节点的左节点
Node rootLeft = new Node();
rootLeft.data = 'C';
//建立根节点的右节点
Node rootRight = new Node();
rootRight.data = 'E';
//建立树形结构
root.left = rootLeft;
root.right = rootRight;
rootLeft.parent = root;
rootRight.parent = root;
/**rootLeft树形型结构的建立*/
//建立rootLeft的左节点
Node rlLeft = new Node();
rlLeft.data = 'A';
//建立rootLeft的右节点
Node rlRight = new Node();
rlRight.data='D';
//建立树形结构
rootLeft.left = rlLeft;
rootLeft.right = rlRight;
rlLeft.parent = rootLeft;
rlRight.parent = rootLeft;
/**rlRight树形型结构的建立*/
//建立rlRight的左节点
Node rlrLeft = new Node();
rlrLeft.data = 'B';
//建立树形结构
rlRight.left = rlrLeft;
rlrLeft.parent = rlRight;
/**rootRight树形型结构的建立*/
//rootRight左节点的建立
Node rrLeft = new Node();
rrLeft.data = 'H';
Node rrRight = new Node();
rrRight.data = 'G';
//建立树形结构
rrLeft.parent = rootRight;
rrRight.parent = rootRight;
rootRight.left = rrLeft;
rootRight.right = rrRight;
/**rrRight树形结构的建立*/
//rrRight左节点的建立
Node rrrLeft = new Node();
rrrLeft.data = 'M';
//建立树形结构
rrrLeft.parent = rrRight;
rrRight.left = rrrLeft;
//返回根节点
return root;
}
//前序遍历
public static void BFTreeOrder(Node node){
if (node != null){
System.out.print(node.data);
BFTreeOrder(node.left);
BFTreeOrder(node.right);
}
}
//中序遍历
public static void IFTreeOrder(Node node){
if (node != null){
IFTreeOrder(node.left);
System.out.print(node.data);
IFTreeOrder(node.right);
}
}
//后序遍历
public static void AFTreeOrder(Node node){
if (node != null){
AFTreeOrder(node.left);
AFTreeOrder(node.right);
System.out.print(node.data);
}
}
public static void main(String[] args) {
Node node = binaryTree.creatTree();
System.out.print("前序遍历:");
binaryTree.BFTreeOrder(node);
System.out.println();
System.out.print("中序遍历:");
binaryTree.IFTreeOrder(node);
System.out.println();
System.out.print("后序遍历:");
binaryTree.AFTreeOrder(node);
}
}
运行结构截图
(三)哈夫曼树
1、哈夫曼树的基本术语
路径: 从开始节点到目的节点所经过的节点共构成的节点集,如从A节点出发,目的节点是D,途径的节点有B、E、F,那么它的路径为:A-B-E-F-D
路径长度: 从开始节点到目的节点所经过的"边"数
节点的带权路径长度: 目的节点的权值与路径长度的乘积
树的带权路径长度(WPL): 所有叶子节点的带权路径长度之和
2、哈夫曼树的定义
在给定的权重节点时,我们构成一颗使其所有节点都是叶子节点的二叉树,并使这个树的带权路径长度最小,我们称这棵树是哈夫曼树,也叫最优二叉树
3、哈夫曼树的构造
假如有5个节点,它们的权值分别为8、5、9、2、4,构造成哈夫曼树步骤如下:
(1)、将这5个节点的权值按升序(从小到大)排序,选择2个权值构成一颗子树,它的父节点的权值是这两个节点的权值之和,最后将选中的2个节点从顺序表中删除,并将所构成的父节点加入顺序表
(2)、将顺序表中的数据按升序排序,选择2个权值构成一颗新子树,它的父节点的权值是这两个节点的权值之和,最后将选中的2个节点从顺序表中删除,并将所构成的父节点加入顺序表
(3)、与(2)的步骤一样
(4)、将顺序表中的数据按升序排序,选择2个权值构成一颗新子树,它的父节点的权值是这两个节点的权值之和,最后将选中的2个节点从顺序表中删除
4、哈夫曼编码
哈夫曼编码是一种不等长的编码,它的主要作用是提高编码的效率,减少总编码的占用空间
例如:假设一段文本,包含58个字符,并且由以下7个字符构成:a,b,c,d,e,f,g;这7个字符出现的频次不同,如何对这7个字符进行编码,使得总编码空间最小。
根据上图可以构造出哈夫曼树,根据哈夫曼树得到哈夫曼表格如下:
ASCII编码(容易实现,但是效率较低):58×8=464位
哈夫曼编码:10×3+15×2+12×2+3×5+4×4+13×2+1×5=146位
注:没有使用哈夫曼树编码而是使用普通二叉树或者完全二叉树编码时会产生二义性,如:
加入二叉树的左分支用0表示,右分支用1表示,b 编码 0,f 编码 1,c 编码 10,a 编码 11;那么0110可以表示为bffb或bfc
代码实现上图表格的哈夫曼树:
/**
* 哈夫曼树
*/
public class haffmanTree{
private int size = 0;
public Node createHaffman(ArrayList<Node> list){
while (list.size() > 1){
//升序排序
haffmanTree.sort(list);
//取出2个权值最小的节点构造子树
//父节点
Node parent = new Node(null,list.get(0).weight+list.get(1).weight);
//左节点
Node left = list.get(0);
//右节点
Node right = list.get(1);
//构造成树形结构
parent.left = left;
parent.right = right;
//移除选取的两个节点并添加新的节点
list.remove(1);
list.remove(0);
list.add(parent);
}
return list.get(0); //返回根节点
}
//排序
private static void sort(ArrayList<Node> nodes) {
if (nodes.size() <= 1)
return ;
/*循环数组长度的次数*/
for (int i = 0; i < nodes.size(); i++){
/*从第0个元素开始,依次和后面的元素进行比较
* j < array.length - 1 - i表示第[array.length - 1 - i]
* 个元素已经冒泡到了合适的位置,无需进行比较,可以减少比较次数*/
for (int j = 0; j < nodes.size() - 1 - i; j++){
/*如果第j个节点比后面的第j+1节点权重大,交换两者的位置*/
if (nodes.get(j + 1).weight < nodes.get(j).weight) {
Node temp = nodes.get(j + 1);
nodes.set(j+1,nodes.get(j));
nodes.set(j,temp);
}
}
}
return ;
}
static class Node<T>{
private Node left; //左节点
private Node right; //右节点
private T data;
private int weight; //权值
public Node(T data, int weight) {
this.data = data;
this.weight = weight;
}
public String toString() {
return "Node[" + weight + ",data=" + data + "]";
}
}
private static void printTree(Node root) {
if (root != null){
System.out.println("[ left:"+root.left+",right:"+root.right+",data:"+root.data+",weight:"+root.weight+"]");
printTree(root.left);
printTree(root.right);
}
}
public static void main(String[] args) {
haffmanTree tree = new haffmanTree();
ArrayList<Node> nodes = new ArrayList<Node>();
//把节点加入至list中
nodes.add(new Node("a", 10));
nodes.add(new Node("b", 15));
nodes.add(new Node("c", 12));
nodes.add(new Node("d", 3));
nodes.add(new Node("e", 4));
nodes.add(new Node("f", 13));
nodes.add(new Node("g", 1));
//进行哈夫曼树的构造
Node root = tree.createHaffman(nodes);
//打印哈夫曼树
printTree(root);
}
}
结果截图:
本次的讲解到此就结束了,如有不足请大家指出,谢谢(*^_^*)