术语 | 描述 |
---|---|
树的结点 | 由一个数据元素及关联其子树的边所组成。 |
结点的边 | 实体与实体之间的逻辑关系 |
结点的路径 | 从根结点到该结点所经历的结点和分支的顺序排列。 结点J的路径:A–>C–>G–>J |
路径的长度 | 结点路径中所包含的分支数。例如:结点J的路径长度为3. |
结点的度 | 该结点所拥有的子树的数目。例如:结点A的度为3,结点B的度为1 |
树的度 | 树中所有结点度的最大值。 |
叶结点 | 树中度为0的结点,也称为终端结点。 |
分支结点 | 树中度不为0的结点,也称为非终端结点。除叶子结点之外的所有结点都是分支结点。 |
孩子结点 | 结点的子树的根节点,也称为子节点。结点A的孩子结点是BCD |
双亲结点 | 某结点有孩子结点,则该结点称为孩子的双亲结点,也称为父节点。 |
子孙结点 | 该结点所有子树中的任意结点。 |
祖先结点 | 该结点的路径中除此结点之外的所有结点。 |
兄弟结点 | 具有同一个双亲的结点。 |
结点的层次 | 规定树中根节点的层次为0,其他结点的层次是双亲结点的层次数加1,结点P层次数为4 |
树的深度 | 树中所有结点的层次数的最大值加1。(a) 深度为1 (b)深度为3 (c)深度为5 |
有序树 | 各节点的所有子树之间从左到右有严格的次序关系,不能交换。 |
无序树 | 树中各节点的所有子树之间没有严格的次序关系。从左到右没有次序之分。 |
森林 | 由m(m>=0)棵互不相交的树所构成的集合 |
有序树
。i 层 0 1 2 3 4 5 ⋯ i 最多结点数 2 0 2 1 2 2 2 3 2 4 2 5 ⋯ 2 i 最多结点数 1 2 4 8 16 32 ⋯ 2 i (i层最多结点数) \begin{array}{c|cc} \hline i层&0&1&2&3&4&5&\cdots&i\\ \hline 最多结点数&2^0&2^1&2^2&2^3&2^4&2^5&\cdots&2^i\\ \hline 最多结点数&1&2&4&8&16&32&\cdots&2^i\\ \hline \end{array} \tag{i层最多结点数} i层最多结点数最多结点数02011212222432384241652532⋯⋯⋯i2i2i(i层最多结点数)
S n = a 1 + a 2 + a 3 + ⋯ + a n − 1 + a n S n = a 1 + a 1 q + a 1 q 2 + ⋯ + a 1 q n − 2 + a 1 q n − 1 q S n = a 1 q + a 1 q 2 + a 1 q 3 + ⋯ + a 1 q n − 1 + a 1 q n S n − q S n = a 1 − a 1 q n S n = a 1 − a 1 q n 1 − q S n = a 1 ( 1 − q n ) 1 − q (等比数列求和) S_n=a_1 + a_2 + a_3 + \cdots + a_{n-1} + a_n \\ S_n=a_1 + a_1q + a_1q^2 + \cdots + a_1q^{n-2} + a_1q^{n-1} \\ qS_n=a_1q + a_1q^2 + a_1q^3 + \cdots + a_1q^{n-1} + a_1q^n \\ S_n-qS_n=a_1-a_1q^{n} \\ S_n=\dfrac{a_1-a_1q^n}{1-q} \\ S_n=\dfrac{a_1(1-q^n)}{1-q} \tag{等比数列求和} Sn=a1+a2+a3+⋯+an−1+anSn=a1+a1q+a1q2+⋯+a1qn−2+a1qn−1qSn=a1q+a1q2+a1q3+⋯+a1qn−1+a1qnSn−qSn=a1−a1qnSn=1−qa1−a1qnSn=1−qa1(1−qn)(等比数列求和)
数的深度 取值 公式 l o g 2 n 的值 ⌊ l o g 2 n ⌋ 的值 3 4 ≤ n < 8 2 2 ≤ n < 2 3 2 ≤ l o g 2 n < 3 2 4 8 ≤ n < 16 2 3 ≤ n < 2 4 3 ≤ l o g 2 n < 4 3 5 16 ≤ n < 32 2 4 ≤ n < 2 5 4 ≤ l o g 2 n < 5 4 6 32 ≤ n < 64 2 5 ≤ n < 2 6 5 ≤ l o g 2 n < 6 5 h 2 h − 1 ≤ n < 2 h h − 1 ≤ l o g 2 n < h h − 1 ( ) \begin{array}{c|c|c} \hline 数的深度 &取值&公式&log_2n的值 & ⌊log2n⌋的值\\ \hline 3& 4 \le n \lt 8& 2^2 \le n \lt 2^3 & 2 \le log_2n \lt 3 & 2 \\ \hline 4& 8 \le n \lt 16& 2^3 \le n \lt 2^4 & 3 \le log_2n \lt 4 & 3 \\ \hline 5& 16 \le n \lt 32& 2^4 \le n \lt 2^5 & 4 \le log_2n \lt 5 & 4 \\ \hline 6& 32 \le n \lt 64& 2^5 \le n \lt 2^6 & 5 \le log_2n \lt 6 & 5 \\ \hline h& & 2^{h-1} \le n \lt 2^h & h-1 \le log_2n \lt h & h-1 \\ \hline \end{array} \tag{} 数的深度3456h取值4≤n<88≤n<1616≤n<3232≤n<64公式22≤n<2323≤n<2424≤n<2525≤n<262h−1≤n<2hlog2n的值2≤log2n<33≤log2n<44≤log2n<55≤log2n<6h−1≤log2n<h⌊log2n⌋的值2345h−1()
数学常识
向下取整的运算称为Floor,用数学符号⌊ ⌋表示;向上取整的运算称为Ceiling,用数学符号⌈ ⌉表示
例如:
⌊59/60⌋=0
⌈59/60⌉=1
⌊-59/60⌋=-1
⌈-59/60⌉=0
序号 i 结论 5.1 双亲节点 i − 1 2 个数 n 2 i + 1 结论 5.2 左孩子 2 i + 2 结论 5.3 :右孩子 5 5 − 1 2 = 2 7 11 无 12 无 3 3 − 1 2 = 1 8 7 2 × 3 + 1 = 7 8 无 4 4 − 1 2 = 1 11 9 2 × 4 + 1 = 9 10 2 × 4 + 2 = 10 ( ) \begin{array}{c|c|c} \hline 序号i& 结论5.1双亲节点 \frac{i-1}{2} & 个数n & 2i+1 & 结论5.2左孩子 & 2i+2 & 结论5.3:右孩子 \\ \hline 5 & \frac{5-1}{2}=2 & 7 & 11 & 无 & 12 & 无\\ \hline 3 & \frac{3-1}{2}=1 & 8 & 7 & 2×3+1 =7 & 8 & 无\\ \hline 4 & \frac{4-1}{2}=1 & 11 & 9 & 2×4+1=9 & 10 & 2×4+2=10\\ \hline \end{array} \tag{} 序号i534结论5.1双亲节点2i−125−1=223−1=124−1=1个数n78112i+11179结论5.2左孩子无2×3+1=72×4+1=92i+212810结论5.3:右孩子无无2×4+2=10()
完全二叉树存储:
用一组地址连续的存储单元从根结点开始依次自上而下,并按层次从左到右存储完全二叉树上的各节点元素,即将完全二叉树编号为i的结点元素存储在下标为i数组元素中。
非完全二叉树:
先在树中增加一些并不存在的虚结点并使其成为一棵完全二叉树,然后用与完全二叉树相同的方法对结点进行编号,再将编号为i的结点的值存放到数组下标为i的数组单元中,虚结点不存放任何值。
顺序存储适用于满二叉树和完全二叉树。
对于非完全二叉树来说,一个深度为h的树,需要的存储单元为2h-1,会造成空间的浪费,如:对于右支树来说,只需要h个存储单元,但是存储的时候却要使用2h-1个空间。
二叉树的链式存储:将二叉树的各个结点随机的存放在位置任意的内存空间中,各个结点之间的逻辑关系通过指针来反映。
链式存储有2种方式:二叉链表存储结构、三叉链表存储结构
二叉链表存储结构示意图
三叉链表存储结构示意图
二叉链式存储结构是二叉树最常用的存储结构。
结点类
public class BiTreeNode {
public Object data; //数据域
public BiTreeNode lchild, rchild; //左、右孩子域
}
二叉树类
public class BiTree {
private BiTreeNode root;
//树的根节点
public BiTree() { //构建一颗空树
this.root = null;
}
public BiTree(BiTreeNode root) { //构建一棵树
this.root = root;
}
}
root.lchild = new BiTreeNode(“B”);
root.rchild = new BiTreeNode(“C”);
二叉树的遍历:沿着某条搜索路径对二叉树中的结点进行访问,使得每个结点均被访问一次,而且仅被访问一次。“访问”的含义较为广泛,例如:输出结点信息。
二叉树有3条搜索路径:
对应3条搜索路径,二叉树有7种遍历方式:
需要遍历的二叉树
若二叉树为空,则为空操作;否则,按自上而下先访问第0层的根节点,然后再从左到右依次访问各层次中的每一个结点。
层次遍历序列
ABECFDGHK
若二叉树为空,则为空操作,否则
先根遍历序列
ABCDEFGHK
若二叉树为空,则为空操作;否则
中根遍历序列
BDCAEHGKF
若二叉树为空,则为空操作;否则
后根遍历序列
DCBHKGFEA
先根序遍历:ABDEGCFH
中根序遍历:DBGEAFHC
后根序遍历:DGEBHFCA
练习2:
先根序遍历:ABDEGJHCFIKL
中根序遍历:DBJGEHACKILF
后根序遍历:DJGHEBKLIFCA
练习3:
先根序遍历:ABCDEFGHK
中根序遍历:BDCAEHGKF
后根序遍历:DCBHKGFEA
public void preRootTraverse(BiTreeNode T) {
if(T != null) {
System.out.print(T.data); //输出根元素
preRootTraverse(T.lchild); //先根遍历左子树
preRootTraverse(T.rchild); //先根遍历右子树
}
}
public void inRootTraverse(BiTreeNode T) {
if(T != null) {
inRootTraverse(T.lchild); //中根遍历处理左子树
System.out.print(T.data); //访问根节点
inRootTraverse(T.rchild); //中根遍历处理右子树
}
}
public void postRootTraverse(BiTreeNode T) {
if(T != null) {
postRootTraverse(T.lchild); //后根遍历左子树
postRootTraverse(T.rchild); //后根遍历右子树
System.out.print(T.data); //访问根结点
}
}
public void preRootTraverse() {
BiTreeNode T = root;
if( T != null ) {
LinkStack S = new LinkStack(); // 创建栈记录没有访问过的右子树
S.push(T); // 将根节点压入栈顶
while(!S.isEmpty()) { // 栈中只要有数据,表示继续遍历
T = S.pop(); // 弹出栈顶数据
System.out.print(T.data); // 结点被访问
while(T != null) { // T指针,访问每一个左孩子
if(T.lchild != null) { // 输出左孩子
System.out.print(T.lchild.data);
}
if(T.rchild != null) { // 将右孩子压栈
T.push(T.rchild);
}
T = T.lchild; // 访问下一个左孩子
}
}
}
}
public void inRootTraverse() {
BiTreeNode T = root;
if(T != null) {
LinkStack S = new LinkStack();
S.push(T); //将根节点压入到栈顶
while( !S.isEmpty() ) { //栈中有数据,表示遍历未完成
//1 将所有的左孩子压栈
while(S.peek() != null) { //栈顶的元素不为空,注意:不是弹栈
// 获得栈顶,
BiTreeNode temp = (BiTreeNode)S.peek();
// 并将左孩子压入栈顶
S.push(temp.lchild);
}
S.pop(); //将栈顶的空元素弹出
//2 依次弹出栈,访问当前节点,如果有右孩子继续压栈
if(! S.isEmpty()) {
T = (BiTreeNode)S.pop();
System.out.print(T.data); //访问栈顶
S.push(T.rchild);
}
}
}
}
public void postRootTraverse() {
BiTreeNode T = root;
if( T != null) {
LinkStack S = new LinkStack();
S.push(T);
// 声明两个变量
Boolean flag; //用于记录是否被访问
BiTreeNode p; //用于记录上一次处理的结点
while(! S.isEmpty() ) {
//1 将所有的左孩子压栈
while(S.peek() != null) { //栈顶的元素不为空,注意:不是弹栈
// 获得栈顶,
BiTreeNode temp = (BiTreeNode)S.peek();
// 并将左孩子压入栈顶
S.push(temp.lchild);
}
S.pop(); //将栈顶的空元素弹出
while( !S.isEmpty() ) {
T = (BiTreeNode) S.peek();
if(T.rchild == null || T.rchild == p) { // 没有右孩子 或 已经访问过
System.out.print(T.data);
S.pop(); //弹出
p = T; //记录刚才访问过的
flag = true; //没有新元素,继续访问
} else {
S.push(T.rchlid);
flag = false; //新右子树添加
}
if(!flag) {
break; //如果有右子树,需要重新开始
}
}
}
}
}
图一 图二 图三 图四 图五 先序遍历 A B C A B C A B C A B C A B C 中序遍历 B A C C B A B C A A C B A B C 特点 B C 不同左分支 B C 相同右分支 ( ) \begin{array}{c|c|c} \hline &图一&图二&图三&图四&图五\\ \hline 先序遍历&ABC&ABC&ABC&ABC&ABC\\ \hline 中序遍历&BAC&CBA&BCA&ACB&ABC\\ \hline 特点&&BC不同左分支&BC相同右分支\\ \hline \end{array} \tag{} 先序遍历中序遍历特点图一ABCBAC图二ABCCBABC不同左分支图三ABCBCABC相同右分支图四ABCACB图五ABCABC()
先序遍历
获得根结点(第一个结点)
。根结点
在中序遍历
确定左子树
和右子树
。练习1:
已知二叉树,先序序列为abcdefg,中序序列为cbdaegf,重建二叉树?
练习2:
已经二叉树,前序遍历序列为{1,2,4,7,3,5,6,8},中序遍历序列{4,7,2,1,5,3,8,6},后序遍历序列是?
练习3:
已知一棵树二叉树的先根遍历和中根遍历的序列分别为:A B D G H C E F I和G D H B A E C I F,请画出此二叉树,并写出它的后根遍的序列?
/** 例如:new BiTree("ABDEGCFH","DBGEAFHC",0,0,8);
* @param preOrder 先序遍历序列
* @param inOrder 中序遍历序列
* @param preIndex 在preOrder中开始位置
* @param inIndex 在inOrder中开始位置
* @param count 结点数
*/
public BiTree(String preOrder,String inOrder,int preIndex,int inIndex,int count) {
if(count > 0) {
//1 通过先序获得根结点
char r = preOrder.charAt(preIndex);
//2 中序中,根结点的位置
int i = 0 ;
for(; i < count ; i ++) {
if(r == inOrder.charAt(i + inIndex)) {
break;
}
}
//3 通过中序,截取左子树和右子树
root = new BiTreeNode(r);
root.lchild = new BiTree(preOrder,inOrder,preIndex+1, inIndex, i).root;
root.rchild = new BiTree(preOrder,inOrder,preIndex+1+i,inIndex + i + 1, count-i-1).root;
}
}
图一 图二 图三 图四 图五 中序遍历 B A C C B A B C A A C B A B C 后序遍历 B C A C B A C B A C B A C B A 特点 C B 相同左分支 B C 不同右分支 ( ) \begin{array}{c|c|c} \hline &图一&图二&图三&图四&图五\\ \hline 中序遍历&BAC&CBA&BCA&ACB&ABC\\ \hline 后序遍历&BCA&CBA&CBA&CBA&CBA\\ \hline 特点&&CB相同左分支&BC不同右分支\\ \hline \end{array} \tag{} 中序遍历后序遍历特点图一BACBCA图二CBACBACB相同左分支图三BCACBABC不同右分支图四ACBCBA图五ABCCBA()
总结:
后序遍历
获得根结点(最后一个结点)
。根结点
在中序遍历
确定左子树
和右子树
。练习1:
已知二叉树,中根遍历序列为:9,3,15,20,7、后根遍历序列为:9,15,7,20,3,重建二叉树?
练习2:
已知二叉树,中根遍历序列为:6,3,4,1,5,8,2,7、后根遍历序列为:3,6,1,8,5,7,2,4,前根遍历序列?
仅使用先根遍历序列无法唯一确定一颗二叉树,例如:“AB”,B可以是左孩子,也可以是右孩子。
在先根遍历序列中加入空树信息,从而确定结点与双亲、孩子与兄弟间的关系,从而唯一确定一颗二叉树。
表名空子树的先序遍历序列:二叉树中每一个结点都必须有孩子或#
建立二叉链表算法分析:
算法
根左右
private static int index = 0; //用于记录preStr的索引值
public BiTree(String preStr) {
char c = preStr.charAt(index++);
if(c != '#') {
root = new BiTreeNode(c); //根
root.lchild = new BiTree(preStr).root; //左
root.rchild = new BiTree(preStr).root; //右
} else {
root = null;
}
}
public BiTreeNode createBiTree(String sqBiTree, int index) {
BiTreeNode root = null;
if(index < sqBiTree.length()) {
root = new BiTreeNode(sqBiTree.charAt(index));
root.lchild = createBiTree(sqBiTree, 2*index+1);
root.rchild = createBiTree(sqBiTree, 2*index+2);
}
return root;
}
( a ) W P L = 5 × 3 + 4 × 3 + 3 × 2 + 2 × 2 + 1 × 2 = 39 ( b ) W P L = 5 × 2 + 4 × 2 + 3 × 3 + 2 × 3 + 1 × 2 = 35 ( c ) W P L = 5 × 2 + 4 × 2 + 3 × 2 + 2 × 3 + 1 × 3 = 33 (带权路径长度) (a) WPL=5×3+4×3+3×2+2×2+1×2 = 39 \\ (b) WPL=5×2+4×2+3×3+2×3+1×2 = 35 \\ (c) WPL=5×2+4×2+3×2+2×3+1×3 = 33 \\ \tag {带权路径长度} (a)WPL=5×3+4×3+3×2+2×2+1×2=39(b)WPL=5×2+4×2+3×3+2×3+1×2=35(c)WPL=5×2+4×2+3×2+2×3+1×3=33(带权路径长度)
( 1 ) 由已知给定的 n 个权值 { w 1 , w 2 , w 3 , . . . , w n } ,构建一个有 n 棵二叉树所构建的森林 F = { T 1 , T 2 , T 3 , . . . T n } ,其中每一棵二叉树中只含一个带权值为 w i 根节点,其左、右子树为空 ( 2 ) 在二叉树森林 F 中选取根节点的权值最小和次小的两棵二叉树,分别把它们作为左子树和右子树 去构建一颗新二叉树,新二叉树的根节点权值为其左右子树根节点的权值之和。 ( 3 ) 作为新二叉树的左右子树的这两棵二叉树从森林 F 中删除,同时加入刚生成的新二叉树 ( 4 ) 重复步骤 ( 2 ) 和 ( 3 ) ,直到森林 F 中只剩一颗二叉树为止,该二叉树就是哈夫曼树。 (1)由已知给定的n个权值\{w_1,w_2,w_3,...,w_n\},构建一个有n棵二叉树所构建的森林 \\ F=\{T_1,T_2,T_3,...T_n\},其中每一棵二叉树中只含一个带权值为w_i根节点,其左、右子树为空\\ \\ (2)在二叉树森林F中选取根节点的权值最小和次小的两棵二叉树,分别把它们作为左子树和右子树\\ 去构建一颗新二叉树,新二叉树的根节点权值为其左右子树根节点的权值之和。 \\ \\ (3)作为新二叉树的左右子树的这两棵二叉树从森林F中删除,同时加入刚生成的新二叉树 \\ \\ (4)重复步骤(2)和(3),直到森林F中只剩一颗二叉树为止,该二叉树就是哈夫曼树。 \\ (1)由已知给定的n个权值{w1,w2,w3,...,wn},构建一个有n棵二叉树所构建的森林F={T1,T2,T3,...Tn},其中每一棵二叉树中只含一个带权值为wi根节点,其左、右子树为空(2)在二叉树森林F中选取根节点的权值最小和次小的两棵二叉树,分别把它们作为左子树和右子树去构建一颗新二叉树,新二叉树的根节点权值为其左右子树根节点的权值之和。(3)作为新二叉树的左右子树的这两棵二叉树从森林F中删除,同时加入刚生成的新二叉树(4)重复步骤(2)和(3),直到森林F中只剩一颗二叉树为止,该二叉树就是哈夫曼树。
练习:
有5个带权结点 {A,B,C,D,E},其权值分别为W={10,30,40,15,6},权值作为结点数据,绘制一颗哈夫曼
编码诉求:对字符集进行二进制编码,使得信息的传输量最小。如果能对每一个字符用不同的长度的二进制编码,并且尽可能减少出现次数最多的字符的编码位数,则信息传送的总长度便可以达到最小。
哈夫曼编码:用电文中各个字符使用的频度作为叶结点的权,构造一颗具有最小带权路径长度的哈夫曼树,若对树中的每个左分支赋予标记0,右分支赋予标记1,则从根节点到每个叶结点的路径上的标记连接起来就构成一个二进制串,该二进制串被称为哈夫曼编码。
练习:p176
已知在一个信息通信联络中使用了8个字符:a、b、c、d、e、f、g和h,每个字符的使用频度分别为:6、30、8、9、15、24、4、12,试设计各个字符的哈夫曼编码。
哈夫曼树进行译码
练习
有5个带权结点 {A,B,C,D,E},其权值分别为W={10,30,40,15,6},权值作为结点数据,绘制一颗哈夫曼,并编写哈夫曼编码
A:1111
B:10
C:0
D:110
E:1110
编码:编码字符串 AABBEDCC–>111111111010111011000
n个权值,组成哈夫曼树节点个数:2n-1
哈夫曼结点类
public class HuffmanNode {
public int weight;// 权值
public int flag; // 节点是否加入哈夫曼树的标志
public HuffmanNode parent,lchild,rchild; // 父节点及左右孩子节点
// 构造一个空节点
public HuffmanNode(){
this(0);
}
// 构造一个具有权值的节点
public HuffmanNode(int weight){
this.weight = weight;
flag=0;
parent=lchild=rchild=null;
}
}
哈夫曼编码类
public class HuffmanTree {
// 求哈夫曼编码的算法,w存放n个字符的权值(均>0)
public int[][] huffmanCoding(int[] W){
int n = W.length; // 字符个数
int m = 2*n -1; //哈夫曼树的节点数
// 构造n个具有权值的节点
HuffmanNode[] HN = new HuffmanNode[m];
int i = 0;
for (; i<n ; i++) {
HN[i] = new HuffmanNode(W[i]);
}
// 创建哈夫曼树
for (i = n; i<m ; i++) {
// 在HN[0...1]选择不在哈夫曼树中,且权值weight最小的两个节点min1和min2
HuffmanNode min1 = selectMin(HN,i-1);
min1.flag = 1;
HuffmanNode min2 = selectMin(HN,i-1);
min2.flag = 1;
// 构造 min1和min2的父节点,并修改父节点的权值
HN[i] = new HuffmanNode();
min1.parent=HN[i];
min2.parent=HN[i];
HN[i].lchild = min1;
HN[i].rchild = min2;
HN[i].weight = min1.weight+min2.weight;
}
// 从叶子到根 逆向求每个字符的哈夫曼编码
int[][] HuffCode = new int[n][n]; // 分配n个字符编码存储空间
for (int j =0;j<n;j++){
// 编码的开始位置,初始化为数组的结尾
int start = n-1;
// 从叶子节点到根,逆向求编码
for(HuffmanNode c = HN[j],p=c.parent;p!=null;c=p,p=p.parent){
if(p.lchild.equals(c)){
HuffCode[j][start--]=0;
}else{
HuffCode[j][start--]=1;
}
}
// 编码的开始标志为-1,编码是-1之后的01序列
HuffCode[j][start] = -1;
}
return HuffCode;
}
// 在HN[0...1]选择不在哈夫曼树中,且权值weight最小的两个节点min1和min2
private HuffmanNode selectMin(HuffmanNode[] HN, int end) {
// 求 不在哈夫曼树中, weight最小值的那个节点
HuffmanNode min = HN[end];
for (int i = 0; i < end; i++) {
HuffmanNode h = HN[i];
// 不在哈夫曼树中, weight最小值
if(h.flag == 0 && h.weight<min.weight){
min = h;
}
}
return min;
}
}
哈夫曼编码会用类似如下格式进行存储
测试类
public class Demo02 {
public static void main(String[] args) {
// 一组权值
int[] W = {6,30,8,9,15,24,4,12};
// 创建哈夫曼树
HuffmanTree tree = new HuffmanTree();
// 求哈夫曼编码
int[][] HN = tree.huffmanCoding(W);
//打印编码
System.out.println("哈夫曼编码是: ");
for (int i = 0; i < HN.length; i++) {
System.out.print(W[i]+" ");
for (int j = 0; j < HN[i].length; j++) {
if(HN[i][j] == -1){
for (int k = j+1; k <HN[i].length ; k++) {
System.out.print(HN[i][k]);
}
break;
}
}
System.out.println();
}
}
}
树转换成二叉树可归纳3步骤:加线、删线、旋转
- 加线:将树中所有相邻的兄弟之间加一条连线。
- 删线:对树中的每一个结点,只保留它与第1个孩子结点之间的连线,删去它与其他孩子结点之间的连线。
- 旋转:以树的根结点为轴心,将树平面顺时针旋转一定角度并做适当的调整,使得转化后所得二叉树看起来比较规整。
二叉树转换成树是树转换二叉树
的逆过程。
树转换成二叉树可归纳3步骤:加线、删线、旋转
- 加线:若某结点是双亲结点的左孩子,则将该结点沿着右分支向下的所有结点与该结点的双亲结点用线连接。
- 删除:将树中所有双亲结点与右孩子结点的连线删除。
- 旋转:对经过(1)、(2)粮补后所得的树以根结点为轴心,按逆时针方向旋转一定的角度,并做适当调整,使得转化后所得的树看起来比较规整。
森林是由若干树组成,任何一棵树和树对应的二叉树其右子树一定是空的。
根据这个规律可以得到森林转化成二叉树的方法:
- 将森林中每棵树转化成二叉树。
- 按照森林的先后顺序,将一颗二叉树视为前一棵二叉树的右子树依次链接起来,从而构成一颗二叉树
孩子链表
的头指针。结点类
public class CSTreeNode {
public Object data; //结点的数据域
publicCSTreeNode firstChild, nextsibling; //左孩子、右兄弟
}
先根遍历序列:
ABEFCDGHIJK
public void preRootTraverse(CSTreeNode T) {
if(T != null) {
System.out.print(T.data);
preRootTraverse(T.firstChild); //先根遍历树中根节点的第一个子树
preRootTraverse(T.nextsibling); //先根遍历树中根节点的其他子树
}
}
后根遍历序列:
EFBCIJKHGDA
public void postRootTraverse(CSTreeNode t) {
if(T != null) {
postRootTraverse(T.firstChild); //后根遍历树中根节点的第一个子树
System.out.print(T.data); //访问数的根节点
postRootTraverse(T.nextsibling); //后根遍历树中根节点的其他子树
}
}
ABCDEFGHIJK
public void levelTraverse(CSTreeNode T) {
if(T != null) {
LinkQueue L = new LinkQueue(); //构建队列
L.offer(T); //根节点入队列
while(!L.isEmpty()) { //队列不为空,处理根结点和左孩子
for(T = L.poll() ; T != null ; T = T.nextsibling) {//依次处理兄弟(右子树)
System.out.print(T.data + " ");
if(T.firstChild != null) { //第一个孩子结点非空入队列
L.offer(T.firstchild);
}
}
}
}
}
先跟遍历顺序是:
ABCEDFGHIJKL
后根遍历序列是:
BECDAGFIKLJH
层次遍历序列:
ABCDEFGHIJKL
=========================================================
好啦,以上就是本期全部内容,能看到这里的人呀,都是能人。
十年修得同船渡,大家一起点关注。
我是♚焕蓝·未来,感谢各位【能人】的:点赞、收藏和评论,我们下期见!
各位能人们的支持就是♚焕蓝·未来前进的巨大动力~
注:如果本篇Blog有任何错误和建议,欢迎能人们留言!