《数据结构》学习笔记(5)——树

一:基本概念

1. 树的定义

树是n( n>=0 )个结点的有限集。n=0时称为空树。

2. 树的特点

在任意一棵非空树中:

  1. 有且仅有一个特定的结点,称为根。
  2. 当 n > 1时,其余结点可分为m( m > 0 )个互不相交的有限集,其中毎一个集合本身又是一棵树,并且称为根的子树。
  3. 森林是m(m>0 )棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

二:结点

(1)定义

树的结点包含一个数据元素及若干指向其子树的分支。

  • 度:结点拥有的子树数称为结点的度。树的度是树内各结点的度的最大值
  • 层次:结点的层次从根开始定义起,根为第一层,根的孩子为第二层。若某结
    点在第i层,则其子树的根就在第i+1层。树的深度或髙度为树中结点的最大层次

已知树T的度为X,若有a个度为D1的结点,b个度为D2的结点,c个度为D3的结点,则树T的叶子结点个数为:

  1. 树的结点个数N=a×D1+b×D2+c×D3+1
  2. 叶子结点个数为=N-(a+b+c)
(2)分类
  • 度为0的结点称为叶结点或终端结点。
  • 度不为0的结点称为非终端结点或分支结点。
  • 除根结点之外,分支结点也称为内部结点。
(3)关系
  • 结点的子树的根称为该结点的孩子,该结点称为孩子的双亲。
  • 同一个双亲的孩子之间互称兄弟。
  • 双亲在同一层的结点互为堂兄弟。
  • 结点的祖先是从根到该结点所经分支上的所有结点。
  • 以某结点为根的子树中的任一结点都称为该结点的子孙。

三:树的分类

  • 树中结点的各子树从左至右是有次序的,不能互换的,则称该树为有序树。
  • 树中结点的各子树从左至右没有次序,否则称为无序树。

四:抽象数据类型

  • InitTree (*T):构造空树 T。
  • DestroyTree (*T):销毁树 T。
  • CreateTree ( *T,definition ):按 definition 中给出树的定义来构造树。
  • ClearTree ( *T ):若树T存在,则将树T清为空树。
  • TreeEmpty (T):若T为空树,返回true,否则返回false。
  • TreeDepth ( T):返回T的深度。
  • Root(T):返回T的根結点。
  • Value (T,cur_e):cur_e是树T中一个结点,返回此结点的值。
  • Assign ( T, cur_e, value ):给树 T 的结点 cur_e 赋值为 value。
  • Parent ( T, cur_e):若cur__e是树T的非根结点,则返回它的双亲,否则返回空。
  • Leftchild(T,cur_e):若cur_e是树T的非叶结点,则返回它的最左孩子,否则返回空。
  • Rightsibling (T, cur__e ):若cur_e有右兄弟,则返回它的右兄弟,否则返回空。
  • Insertchild (*T,*p,i,c):其中p指向树T的某个结点,i为所指结点p的度加上1,非空树c与T不相交,操作結果为插入c为树T中p指结点的第i棵子树。
  • DeleteChild ( *T, *p, i ):其f p指向树T的某个结点,i为所指结点p的度,操作结果为删除T中p所指结点的第i棵子树。

五:存储结构

1. 双亲表示法

(1):定义

用一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲结点到链表中的位置。

(2):属性
  • 数据域:存储结点的数据信息
  • 指针域:指向双亲在数组中的下标
  • 长子域:指向左孩子在数组中的下标
  • 右兄弟域:指向右兄弟在数组中的下标

2. 孩子表示法

(1):多重链表表示法的不足

多重链表表示法:每个结点有多个指针域,其中每个指针指向一棵子树的根结点。

确定指针域的个数:

  1. 每个结点指针域的个数都等于树的度(适合树的各结点度相差很小)
  2. 每个结点指针域个数等于该结点的度(要维护结点的度的数值,耗费时间)
(2):定义

把毎个结点的孩子结点排列起来,以单链表作存储结构,如果是叶子结点则表为空。
然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中

(3):属性

1:孩子链表

  • 数据域:结点在表头数组的下标
  • 指针域:指向下一个孩子结点

2:表头数组

  • 数据域:结点的数据信息
  • 指针域:指向孩子链表的头结点
  • 双亲域:指向双亲结点(也叫双亲孩子表示法)

3. 孩子兄弟表示法

(1):定义

用链表存储树的结点,同时在每个结点中,指示孩子和兄弟。

(2):属性
  • 数据域:存储数据信息
  • 孩子域:指向第一个孩子结点的存储地址
  • 兄弟域:指向右兄弟结点的存储地址

六:遍历

树的遍历:

  • 先根遍历(二叉树的前序遍历):先访问树的根结点,然后依次先根遍历根的每棵子树。
  • 后根遍历(二叉树的中序遍历):先依次后根遍历每棵子树,然后再访问根结点。

森林的遍历:

  • 前序遍历:先访问森林中第一棵树的根结点,然后再依次先根遍历根的每棵子
    树,再依次用同样方式遍历除去第一棵树的剩余树构成的森林。
  • 后序遍历:是先访问森林中第一棵树,后根遍历的方式遍历毎棵子树,然后再
    访问根结点,再依次同样方式遍历除去第一棵树的剩余树构成的森林。

七:二叉树

1. 定义

二叉树是n(n>=O)个结点的有限集合,该集合或者为空集{称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

2. 特点

  • 每个结点最多有两棵子树.所以二叉树中不存在度大于2的结点。(可以没有子树或者有一棵子树)
  • 左子树和右子树是有顺序的,次序不能任意颠倒。
  • 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

3. 基本形态

  • 空二叉树
  • 一个根节点的二叉树
  • 根节点+左孩子的二叉树
  • 根节点+右孩子的二叉树
  • 根节点+左孩子+右孩子的二叉树

4. 特殊形态

(1):斜树(特殊线性表)
特点:

每一层都只有一个结点,结点的个数与二叉树的深度相同。

分类:
  1. 所有结点都是只有左子树的二叉树叫左斜树。
  2. 所有结点都是只有右子树的二叉树叫右斜树。
(2):满二叉树

在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。

特点:
  • 叶子只能出现在最下一层。出现在其他层就不可能达成平衡。
  • 非叶子结点的度一定是2。
  • 在同样深度的二叉树中,满二叉树的结点个数最多,叶子败最多。
(3):完全二叉树

对一棵具有n个结点的二叉树按层序编号,如果编号i (1<= i <= n)的结点与同样深度的满二叉树中编号为 i 的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树

给每个结点按照满二叉树的结构逐层顺序编号,如果编号出现空档,则不是完全二叉树

区别

满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满的。

特点
  • 叶子结点只能出现在最下两层。
  • 最下层的叶子一定集中在左部连续位置。
  • 倒数二层,若有叶子结点,一定都在右部连续位置。
  • 如果结点度为1,则该结点只有左孩子,不存在只有右子树的情况。
  • 同样结点数的二叉树,完全二叉树的深度最小。

5. 性质

  • 在二叉树的第i层上至多有2n-1个结点(i>1)
  • 深度为k的二叉树至多有2k-1个结点(k>1)
  • 对任何一棵二叉树T,如果其叶子结点数为n0,度为1的结点树为n1,度为2的结点数为n2,则树T的结点总数 N=n0+n1+n2,树T的分支线的总数为 L=N-1=n1+2×n2,所以可以得出 n0=n2+1,n1=0或n1=1
  • 具有n个结点的完全二叉树的深度为(log2n)+1
  • 如果对一棵有n个结点的完全二叉树(其深度为(log2n)+1 )的结点按层序编号(从第1层到第(log2n)+1 层,每层从左到右),对任一结点 i(1<i<n)
  1. 如 果 i = 1 , 则结点 i 是二叉树的根,无双亲;如 果 i > 1 , 则 其 双 亲 是 结 点 i/2
  2. 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i
  3. 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1

6. 存储结构

(1):顺序存储结构(用于完全二叉树)

二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系。
可以按照完全二叉树编号(把不存在的结点设置为^),但是会浪费存储空间。

(2):二叉链表

用链表表示二叉树,二叉树每个结点最多有两个孩子,所以每个结点包含一个数据域和两个指针域,分别指向左右孩子。如果再用一个指针域指向双亲,就成为三叉链表。

7.遍历(访问根的先后顺序)

指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得毎个结点被访问一次且仅被访问一次。

(1):前序遍历(栈实现)

规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。

(2):中序遍历(栈实现)

规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

(3):后序遍历(栈实现)

规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

(4):层序遍历(队列实现)

规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。

8. 构建

  1. 构建扩展二叉树。将二叉树中每个结点的空指针引出一个虚结点,其值为特定值。
  2. 遍历一个二叉树序列,构造结点N。如果序列的值为特定值,则N为空结点,否则,将序列中的值赋给N。
  3. 通过二叉树的遍历,对结点N递归构造树。

9. 线索二叉树

(1):定义

把指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树 。

可以在每个结点再增加两个标志域,标志指针域是指向前驱结点/后继结点,还是左结点/右结点。

可以在二叉树线索链表上添加一个头结点,它的左孩子结点指向根节点,它的后继结点指向最后的结点。同时树的第一个结点的前驱和最后一个结点的后继指向头结点。这样,既可以从第一个结点起顺后继进行遍历,也可以从最后一个结点起顺前驱进行遍历。

(2):特点

所用的二叉树需经常遍历或査找结点时需要某种遍历序列中的前驱和后继。

(3):线索化(中序)

在遍历二叉树的过程中,修改结点的空指针为线索。

  1. 递归遍历树
  2. 如果结点没有左孩子,则赋值前驱结点为刚刚访问的结点并修改标志域
  3. 如果刚刚访问的结点没有右孩子,赋值刚刚访问的结点的后继结点为当前结点并修改标志域

对于前序线索化,只能知道后继,无法知道前驱,需要双亲结点
对于后序线索化,只能知道前驱,无法知道后继,需要双亲结点

(3):遍历(中序)
  1. 从头结点的左孩子结点——根结点N0开始遍历
  2. 从根结点N0开始向左循环,二叉树的第一个结点一定是最左下的,并且由于是第一个,肯定有前驱结点指向空,而不可能有左孩子。找到标志域为前驱结点——二叉树的第一个结点N1。
  3. 如果该结点的另一个标志域为后继结点,则找到下一个结点N2。也就是说N1是一个没有左右孩子的根结点,上一棵树按左-根-右遍历
  4. 否则,N1是一个有右孩子的根结点,上一棵树按根-右遍历。它的下一个结点一定是其右子树中序遍历后的第一个结点,也就是最左下的结点。从N1的右子树的根节点出发向左循环,找到标志域为前驱结点——二叉树的第二个结点N2。
  5. 直到结点为头结点或者空树,遍历结束

八:二叉树与树的转化

树 —>> 二叉树

  1. 兄弟加线。在所有兄弟结点之间加一条连线。
  2. 孩子去线。对树中每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线。
  3. 层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。结点的第一个孩子是二叉树结点的左孩子,结点的兄弟结点是它的右孩子。

森林 —>> 二叉树

  1. 把每个树转换为二叉树。
  2. 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。

二叉树 —>> 树

  1. 加线。若某结点的左孩子结点存在,则将这个左孩子的n个 右孩子结点都作为此结点的孩子。将该结点与这些右孩子结点用线连接起来。
  2. 去线。删除原二叉树中所有结点与其右孩子结点的连线。
  3. 层次调整。使之结构层次分明。

二叉树 —>> 森林

判断一棵二叉树能够转换成一棵树还是森林,标准很简单,那就是只要看这棵二
叉树的根结点有没有右孩子,有就是森林,没有就是一棵树。

  1. 从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,直到所有右孩子连线都删除为止,得到分离的二叉树。
  2. 再将每棵分离后的二叉树转换为树即可。

九:应用——赫夫曼树

1. 基本概念

路径长度:从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径的分支数目就是路径长度。树的路径长度就是从树根到每一结点的路径长度之和。

带权路径长度:对于带权的结点,结点的带权的路径长度为从该结点到树根之间的路径长度与结点的权的乘积。树的带权路径长度为树中所有叶子结点的带权路径长度之和。

赫夫曼树:带权路径长度WPL最小的二叉树称做赫夫曼树(最优二叉树)。

2. 构建赫夫曼树(严格的二叉树,只有0度和2度)

  1. 先把有权值的叶子结点按照权值从小到大的顺序排列成一个有序序列L,每个叶子结点为一个独立的二叉树
  2. 取头两个最小权值的结点N1,N2作为一个新节点P1的两个子结点,构建新二叉树,注意相对较小的是左孩子,另一个是右孩子。新结点的权值为两个叶子权值的和。
  3. 将新的二叉树插入到L,对L重新排序。
  4. 重复构造二叉树,直到序列中只剩下一个二叉树。

3. 使用赫夫曼编码

  • 构造:对于一个字符串,把字符当做叶子节点,字符出现的频率为叶子节点的权值,构建赫夫曼树。
  • 编码:赫夫曼树的左孩子为0,右孩子为1。对每个字符用其从树根到叶子所经过路径的0或1来编码。
    (任一字符的编码都不是另一个字符的编码的前缀,也叫前缀编码
  • 解码:根据赫夫曼树,从根结点开始,遇到0则向左移动,遇到1则向右移动,直到叶子结点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值