数据结构复习——树

有且仅有一个特定的结点称为根

树中也用前驱和后继来描述结点之间的关系

根结点:树中无前驱的节点

结点的度:结点拥有的子树数目

树的度:树内结点的度的最大值

叶子结点:没有子树

内部结点:根结点之外的分支结点

树的深度:树的最大层次,即有几层

有序树:树中结点的各个子树从左到右有次序(最左边的为第一个孩子)

无序树:换了位置也还是这颗树

森林:m颗互不相交的树的集合,给森林加上一个双亲结点就成了一棵树

存储结构

  • 双亲结点法

例如ABC的双亲都在0号位置,则说明R有ABC三个孩子

结点类型描述:

树的描述:

  •  孩子链表

 找孩子容易,找双亲难

 

 带双亲的孩子链表

 

  •  二叉树表示法

用二叉链表作为媒介可以转换,给定一颗树可以找到唯一的对应的二叉树 

将树转换为二叉树:兄弟节点之间连线,孩子和双亲之间的连线只留下第一个孩子的 

将二叉树转换为树:如果该节点是其双亲的左孩子,则将该节点的右孩子与该节点的双亲连起来,然后去掉原来的连线

将森林变成二叉树:将所有树变成二叉树,然后将各个二叉树的根节点相连成为一棵树,然后把最左边的树的根节点作为新树的根节点

将二叉树变成森林:从根节点开始去掉与右孩子的连线,以及右孩子的右孩子等

树的遍历

  • 先根遍历:树不空,则先遍历根节点,再遍历各个子树
  • 后根遍历:树不空,则先遍历各个子树再访问根节点
  • 层次遍历:树不空,则从上到下从左到右访问各个结点

森林的遍历

对森林中的树依次递归:

先序遍历:第一棵树的根节点——子树森林——其他树森林

中序遍历:子树森林——第一颗树根节点——其他树森林

后序遍历:子树森林——其他树森林——第一棵树根节点 

二叉树

二叉树:每个结点最多有两个叉,普通的多叉树要转换为二叉树运算简单。由一个根节点和两个互不相交的左子树和右子树的二叉树组成,可以有空的左子树或者右子树,二叉树即使只有一个子树也要区分是左子树还是右子树

性质

深度为k的二叉树至少有k个结点

从下往上看,只有根节点没有双亲,如果有n个结点的话所有节点都和双亲有一条边除了根节点,于是n-1;从上往下看,所有度为2的产生两条边,度为1产生一条边 

满二叉树

一个深度为K的二叉树而且有(2的k次方-1)个结点

特点:每层都满,叶子结点全部在最底层

结点编号:从根节点开始,从上到下,从左往右

完全二叉树

深度为k的结点个数为n的二叉树,当且仅当每个结点都与其对应的满二叉树的结点一一对应的上时才能叫做完全二叉树(位置上的编号要对应)

 在满二叉树中,从最后一个结点开始连续的去掉任意个数的结点,都是完全二叉树

特点:叶子结点只能在层次最大的两层上;对于任意结点,如果右子树的最大层次为i,则左子树的最大层次要么是i要么是i+1

性质:

[]表示不大于该数的数

因为完全二叉树的最后一层全满为(2的k次方-1)个结点,而最少要(2的k-1的次方-1+1)个结点

 二叉树的顺序存储

按照满二叉树的结点层次编号,依次存放二叉树的数据元素

缺点:浪费存储空间,如果是单支树深度为k的需要2的k次方-1个存储空间

不过结点关系蕴含在存储位置中,适用于满二叉树和完全二叉树

Typedef定义一个数组类型SqBiTree,TElemType表明数组中的数据元素,可以是int也可以是其他等,然后用SqBiTree定义bt,这个数组名就叫bt

 二叉树的链式存储

每一个结点有一个数据域和两个指针域,分别是lchild和rchild分别指向左右孩子

嵌套(递归)定义,为了用起来方便,一个是普通的定义结点类型BiNode,另一个是定义指向这样一个结点的指针的类型*BiTree

 头指针指向根节点

三叉链表

 多了个指向双亲结点的指针

遍历二叉树

L:遍历左子树    D:遍历根节点   R:遍历右子树

若规定先左后右:先中后是对于根而言的

先序遍历:访问根——左子树——右子树

中序遍历:访问左子树——根——右子树

后序遍历:访问左子树——右子树——根

  • 先序遍历

这棵树怎么表示呢,用BiTree定义指向根节点的指针

Visit(T)泛指操作,比如printf

反复执行,一层一层执行,是空就返回

  •  中序遍历

递归算法:

非递归算法:建立一个栈,根节点进栈,遍历左子树,如果左子树为空则根节点出栈,不为空则遍历完后使根节点出栈,输出根节点,遍历右子树

  •  后序遍历

 如果去掉输出语句Visit(T),则三种算法都是相同的

  •  层次遍历

看出从上到下一层一层访问,每一层从左到右访问

使用顺序循环队列:将根节点入队,队不空时循环,从队头出队一个结点,将该结点的左右孩子从队尾入队,依次类推

 遍历算法的应用

  • 按先序遍历建立二叉树的二叉链表

从键盘键入二叉树的结点信息,补充二叉树的空结点,用#表示空结点,这样二叉树就可以唯一了,建立二叉树的存储结构,按照二叉树先序遍历方式建立

读入字符,读到#表示结点为空,否则就分配内存给该结点,分配不成功就退出,并给结点的数据域赋值,创建该结点的左子树与右子树,不需要循环,因为是递归的思想,return就是递归中“归”的过程,一层一层返回

  •  复制二叉树

先看要复制的树是否是空树,如果是空树的话就返回空结点,如果不是的话分配一块结点的空间,将结点的数据域复制到新的树结点,利用递归分别建立左子树和右子树

  • 计算二叉树的深度

递归返回的+1起到了计数器的作用

另一种思想(从弹幕获得):可以计算出所有的结点然后利用公式求得深度

  •  计算二叉树中结点个数

  • 计算叶子结点数 

如果树不为空,则看左子树和右子树是否为空,如果左子树和右子树都是空就说明是叶子结点,否则就不是

 线索二叉树

对于这里的前驱和后继,指的是在给出的遍历字符串中的前驱和后继,比如G的前驱为E,G的后继为D

 因此结点的结构为:

为了解决遍历字符串中第一个字母和最后一个字母没有前驱或者没有后继的问题,设置一个头结点,让这两个结点指向头结点,达到循环链表的目的 

 哈夫曼树

e.g.:为各个同学的成绩评等级,由于正态分布头尾的学生少,中部同学要判断则要比较好几次,当数据量大时所需时间量大,显然两种判断树的效率不同

路径:从一个结点到另一个结点之间的分支构成的这两个结点间的路径

路径长度:两个结点间的分支数

树的路径长度:根节点到每个结点的路径长度的和,记作TL

结点树相同的二叉树中,完全二叉树是路径长度最短的一棵树

权:结点的赋值

结点的带权路径长度:从根节点到该节点的路径长度与该节点权的乘积

树的带权路径长度:树中所有叶子结点带权路径长度的和,记作WPL

 哈夫曼树也叫最优二叉树,带权路径长度最短的树,权值越大的结点离根节点越近,哈夫曼树不唯一

哈夫曼算法

给定多少个带权结点,就将这些结点全作为根节点构成森林,选用两个权值最小的结点作为树的左结点和右结点,它们的共同双亲结点权值为它们俩权值的和,从待选结点森林中去掉这两个结点,增加这两个结点的共同双亲结点,重复步骤知道待选结点森林中只有一棵树

如果有n个结点,经过n-1次合并,要产生2n-1个结点

 存储结构

采用顺序存储结构——一维数组

结点类型定义:用HuffmanTree H定义的可以是一个指针,也可以是数组(因为是数组名,指向数组首地址)

 因为n个结点产生2n-1个结点,为了方便,数组下标从1到2n,不使用0下标

算法实现:先把所有结点都当作根节点,然后选择最小的2和3产生权值为5的9号新节点,删去两个结点的操作即为让这两个结点的parent不为0,依次类推,当表中parent只有一个值为0即只有一个根节点时操作结束,建立哈夫曼树完毕

  • 初始化数组的parent、lch、rch的值为0,然后输入填充各个结点的weight值

  • 进行n-1次合并,产生n-1个新节点,并从第n+1个位置插入结点,从parent=0的结点中找出两个weight最小的结点,修改HT[].parent的值,修改新结点的HT[]的weight值和左右孩子

哈夫曼编码 

让编码需要的字符尽可能少,且一个码不能是另一个码的前缀

字符种类数为n,用哈夫曼树得到一棵树,每个叶子结点代表一个字符,那么得到哈夫曼编码的方法是从每一个叶子结点开始找到它的parent结点然后确认是左孩子还是右孩子,左孩子就是0右孩子为1,直到找到的结点parent为0说明已经到达根节点,每个叶子结点回溯到根节点的次数不确定,于是回溯循环条件为while(f!=0)。每个结点回溯的结果存在一个cd数组里面,这个cd数组的长度设为n(根据二叉树的性质最多有n层),然后回溯的结果倒着存入也就是先存入n-2的位置中,n-1的位置存\0结束标志。对于所有的叶子结点建立一个表,用HC存所有字符的编码

 具体代码如下:(HC是一个表如上图,头指针指向表中每一个数组的首地址,相当于二维数组)

 利用哈夫曼编码进行文件的编码与解码

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值