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

一:基本概念

1. 树的定义

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

2. 树的特色

在任意一棵非空树中:数组

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

二:结点

(1)定义

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

  • 度:结点拥有的子树数称为结点的度。树的度是树内各结点的度的最大值
  • 层次:结点的层次从根开始定义起,根为第一层,根的孩子为第二层。若某结
    点在第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):多重链表表示法的不足

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

肯定指针域的个数:排序

  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则向右移动,直到叶子结点。