树
n(n>=0)个结点的有限集
空树:n=0
在非空树中:
有且仅有一个特定的根root结点;
n>1时,其余结点可以分为m个互不相交的有限集,其中每个集合也是一棵树,根的子树
一对多
结点的度:结点拥有的子树数
树的度:树内各结点的度的最大值
叶(终端)结点:度为0
分支(非终端)结点:度不为0
内部节点:除根结点之外的分支结点
结点—结点的子树的根
双亲—孩子
统一个双亲的孩子互称兄弟
祖先:根到该结点所经分支上的所有结点
子孙:以结点为根的子树的任意结点
根:无双亲,叶结点:无孩子
中间结点:一个双亲多个孩子
层次:从根开始定义,根是第1层,根的孩子是第2层依次类推
双亲在同一层的结点互为堂兄弟
树的深度\高度:结点的最大层次
有序树:各子树从左至右是有次序的,不可互换
森林:m(m>=0)棵互不相交的树的集合
对树中的每个结点而言,子树的集合即为森林
抽象数据类型
树(tree)
Data
树是由一个根结点和若干棵子树构成。树中结点具有相同数据类型及层次关系
Operation
InitTree(*);构造空树T。
DestroyTree(*T) : 销毁树T。
CreateTree(*Tdefinition) : 按definition中给出树的定义来构造树。
ClearTree(*T) : 若树存在,则将树I清为空树。
TreeEmpty(T) : 若为空树,返回true,否则返回false。
TreeDepth(T) : 返回T的深度。
Root(T) : 返回T的根结点。
Value(T, cur e) : cur e是树T中一个结点,返回此结点的值。
Assign(T, cure, value) : 给树的结点cure值为value。
Parent(T, cure) : 若cure是树的非根结点,则返回它的双亲,否则返回空。
LeftChild(T, cure) : 若cur e是树里的非叶结点,则返回它的最左孩子,否则返回空。
RightSibling(T, cure) : 若cure有右兄弟,则返回它的右兄弟,否则返回空。
InsertChild(*T, *p, i, c) : 其中p指向树里的某个结点,i为所指结点p的度加上1, 非空树c与T不相交,操作结果为插入c为树T中p指结点的第i棵子树。
DeleteChild(*T, *p, i) : 其中p指向树里的某个结点,i为所指结点p的度,操作 结果为删除T中p所指结点的第i棵子树。
双亲表示法
在每个结点中,指示双亲结点的位置
数据域:存储结点的数据信息
指针域:存储结点的双亲在数组的下标
找双亲结点时间复杂度O(1)
找孩子结点,需要遍历整个结构
关注结点的孩子,增加长子域:结点最左边孩子的域
关注结点的兄弟,增加右兄弟域:右兄弟下标
孩子表示法
多重链表表示法:
每个结点有多个指针域,每个指针指向一棵子树的根结点
树的每个结点的度,孩子个数不同
1、指针域的个数以树的度(结点度的最大值)为准
当结点度相差很大时,浪费空间
2、每个结点指针域的个数等于结点的度,取一个位置存指针域个数
不会有空间浪费
结点链表不同结构,需要维护结点的度的值,时间损耗
孩子表示法
n个结点
每个结点的孩子,排列做单链表,n个孩子链表
叶子结点链表为空
n个头指针,做线性表,一维数组
便于遍历整棵树,找某个结点的孩子,兄弟,不方便找双亲
双亲孩子表示法
在孩子表示法的基础上加双亲,方便找双亲
孩子兄弟表示法
结点的第一个孩子,存在就是唯一
第一个孩子的右兄弟,存在就是唯一
两个指针指向第一个孩子和其右兄弟
方便查找某个结点的某个孩子
二叉树
n(n>=0)个结点的有限集合
n=0,空二叉树
n>1,一个根节点、根节点的(两颗互不相交的)左右子树
每个结点最多有2棵子树,0,1,2
左右子树有顺序,不可随意颠倒
一棵子树时也要区分左右
5种基本形态:
- 空二叉树
- 只有一个根节点
- 根节点+左子树
- 根节点+右子树
- 根节点+左子树+右子树
3个结点的树的5种情况
特殊二叉树:
- 斜二叉树:每层只有一个结点,结点个数=二叉树的深度
左斜树:所有结点都只有左子树
右斜树:所有结点都只有右子树 - 满二叉树:每个结点都有左右子树(每个结点的度都为2),所有叶子结点都在同一层(最下层)
同样深度的二叉树,满二叉树的结点个数最多,叶子最多 - 完全二叉树:按层序排号,编号和满二叉树相同的结点位置也相同
叶子结点只能在最下两层,
结点度为1时,只有左孩子
最下层的叶子在左边连续,倒数第二层的叶子在右边连续
同样结点树的二叉树,完全二叉树深度最小
性质
- 第 i 层上至多有2i-1个结点(i>=1)
- 深度为 k 的二叉树至多有2k-1个结点
- 叶子结点个数n0,度为2的结点个数n2,n0=n2+1;
- 具有n个结点的完全二叉树深度为(小于log2n的最大整数)+1,具有n个结点的满二叉树深度为log2(n+1)
- n个结点的完全二叉树,层序编号,对任一结点i
i=1,结点为根,i>1,双亲是结点(小于i/2的最大整数)
2i>n,i 为叶子,无左孩子;否则左孩子是2i
2i+1>n,i 无右孩子;否则右孩子是2i+1
顺序存储
一维数组存储结点,数组下标体现结点之间的逻辑关系
按完全二叉树的层序编号,不存在的结点设为空
最适合完全二叉树,其他情况浪费空间
二叉链表
二叉链表:一个数据域,两个指针域
遍历二叉树
从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问有且仅有1次
前序
根左右
二叉树为空,返回
根开始,前序左子树,前序右子树
中序
左根右
二叉树为空,返回
中序左子树,访问根节点,中序右子树
后序
左右根
二叉树为空,返回
后序序左子树,后序右子树,访问根节点
层序
根开始,从上而下,从左到右
已知前序+中序,可以确定一棵二叉树
已知后序+中序,可以确定一棵二叉树
建立二叉树
扩展二叉树:二叉树的每个结点的空指针引出一个虚结点,特定值,比如‘#’
通过扩展二叉树的一个遍历序列确定树
键盘输入扩展二叉树的前序序列AB#D##C##,一次性输入,递归生成树
也可以用创建结点的函数,反复调用
线索二叉树
n个结点,2n个指针域,n+1个空指针域,浪费空间
结点的前驱和后继每次都需要遍历
线索:指向前驱和后继的指针
线索链表:加上线索的二叉链表
线索二叉树:加上线索的二叉树
线索化:二叉树以某种次序遍历变为线索二叉树的过程,即把二叉链表中的空指针改为前驱和后继
增加标志ltag、rtag确定是指向左孩子\右孩子还是前驱\后继
tag是0,1布尔型变量,比指针变量占用内存小
在二叉树线索链表上添加一个头结点
充分利用空指针,减少空间浪费
创建时的一次遍历就可以一直使用前驱后继的信息,减少时间
需要经常遍历,或需要前驱和后继,适合使用线索二叉树
哈夫曼树
文件压缩
哈夫曼编码:基本压缩编码方法
哈夫曼树:带权路径长度最小的二叉树
两个结点之间的路径:树中一个结点但到另一个结点之间的分支
路径长度:路径上的分支数目
树的路径长度:从树根到每一结点的路径长度之和
结点的带权路径长度:该结点到树根之间的路径长度与结点上权的乘积
树的带权路径长度:结点带权路径长度之和
生成哈夫曼树
1、有权值的叶子从小到大排序,A5,E10,B15,D30,C40
2、最小权值的2个结点AE作为新结点N1的两个子结点,最小的做左孩子,N1权值为5+10=15
3、N1代替AE插入到序列N115,B15,D30,C40
4、重复步骤2
5、全部插入,完成
算法描述
哈夫曼编码
远距离传输电报的数据传输的最优问题
需要编码的字符集:{d1,d2,d3,…dn},作为叶子结点
字符在电文中出现的次数或频率集合:{w1,w2,w3,…wn},作为相应叶子节点的权值
构造的哈夫曼树
左分支:0,右分支:1
从根节点到叶子结点经过的路径分支组成的0,1的序列,则为对应字符的编码
编码和解码需要约定好相同的哈夫曼编码规则
树,森林,二叉树
树:一个结点有任意多个孩子
二叉树:只有左右孩子
森林:若干棵树
树转换为二叉树
1、所有兄弟结点之间加一条线
2、每个结点,只保留和第一个孩子之间的连线,去掉和其他孩子的连线
3、层次调整,结点:第一个孩子是左孩子,兄弟是右孩子
森林转换为二叉树:
1、每棵树都转换为二叉树
2、第一棵树不动,后一棵二叉树的根节点做前一棵树的根节点的右孩子
二叉树转换为树
1、结点的左孩子的n个右孩子结点,都作为此结点的孩子
2、删除二叉树中所有结点和右孩子的连线
3、层次调整
二叉树转换为森林
二叉树的根节点有右孩子就是森林,没有就是一棵树
1、根节点开始,和右孩子的连线删除
2、分离的二叉树转换为树
树的遍历
1、先根遍历,先访问根,再依次先根遍历子树
2、后根遍历,先依次后根遍历子树,再访问根
森林遍历
1、前序,先第一棵树的根,再依次遍历根的每棵子树,再同样方式遍历剩余树(和二叉树的前序遍历结果相同)
2、后序,先第一棵树,后根遍历每棵子树,再访问根结点,再同样方式遍历剩余树(和二叉树的中序遍历结果相同)