《大话数据结构》学习笔记--chapter 6

来源:互联网 发布:数控锥度编程 编辑:程序博客网 时间:2024/06/05 06:07

Chap 6 树

6.2 树的定义

树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:

l  有且仅有一个特定的称为根(Root)的结点;

l  当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1.T2…Tm,其中每一个集合本身又是一棵树,并且称为根的子树(Sub Tree).

l  n>0时,根节点是唯一的;

l  m>0时,子树的个数没有限制,但他们一定是互不相交的;

6.2.1 结点分类

结点拥有的子树称为结点的(Degree),度为0的结点称为叶节点(Leaf)或终端节点,不为0的结点称为非终端节点或分支结点。除了根节点之外,分支结点也称为内部结点树的度是树内各结点的度的最大值;

6.2.2 结点间关系

结点的子树称为该结点的孩子(Child),相应的该结点称为孩子的双亲(Parent)。同一个双亲的孩子之间互称兄弟(Sibling)结点的祖先是从根到该结点所经分支上的所有结点,以某一结点为根的子树中的任一结点都称为该结点的子孙

6.2.3 树的其他相关概念

节点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。双亲在同一层的结点互称为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度

如果树中结点的各子树看成从左至右是有次序的,不能互换的,则乘该树为有序树,否则称为无序树

森林(Forest)是m(m>=0)棵互不相交的树的集合;

 

6.3 树的抽象数据类型

InitTree(*T):构造空树;

DestroyTree(*T):销毁树;

ClearTree(*T):若存在则清空;

。。。。。。。。

 

6.4 树的存储结构

6.4.1 双亲表示法

在每个结点中,附设一个指示器指示其双亲结点在数组中的位置;

[data][parent]

data 数据域,parent 指针域,用来存储双亲在数组中的下标,根结点的指针域为-1;

还可以增加一个域用来存储第一个孩子(firstChild)或兄弟(rightSibling),没有则为-1;

[data][parent] [firstChild]

[data][parent] [rightSibling]

 

6.4.2 孩子表示法

每个结点有多个指针域,其中每个指针域指向一棵子树的根结点,这种方法叫做多重链表表示法

l  方案一:指针域的个数等于树的度,但对于树中各结点的度相差很大时,浪费空间,因为有很多结点的指针域是空的;date数据域/child1-childd指针域

[data] [child1] [child2] [child3] [……] [childd]

 

l  方案二(即孩子表示法):每个结点指针域的个数等于该结点的度,专门取一个位置来存储结点指针域的个数;即date数据域/degree度域/child1-childd指针域;

[data][degree] [child1] [child2] [……] [childd]

 

²  把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空,然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一位数组中;包含两种结点结构:

Ø  表头数组的表头结构(指向的是自己的孩子):data数据域,存储某结点的数据信息;firstchild头指针域,存储该结点的孩子链表的头指针;

[data] [firstChild]

Ø  孩子链表的孩子结点(指向的是自己的兄弟):child数据域,存储某个结点在表头数组中的下标;next指针域,存储指向某结点的下一个孩子结点的指针;

[child] [next]

**双亲孩子表示法:在表头结构中添加一个parent数据域,值为该结点的双亲;

6.4.3 孩子兄弟表示法

任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的,因此设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟

[data][firstChild] [rightSibling]

data数据域firstchild指针域,存储第一个孩子的存储地址;rightsibling指针域,存储该结点的右兄弟结点的存储地址;(可以增加一个parent指针域来查找双亲)(此方法把一棵复杂的树变成了一棵二叉树)

 

6.5 二叉树的定义

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

6.5.1 二叉树的特点

l  每个结点最多有两棵子树,不存在度大于2的结点;

l  左子树和右子树是有顺序的,次序不能颠倒;

l  某个结点只有一棵子树,也要区分左子树和右子树;

l  二叉树的五种形态:

n  空二叉树;

n  只有一个根结点;

n  根结点只有左子树;

n  根结点只有右子树;

n  根结点左右子树都有;

6.5.2 特殊二叉树

l  斜树:左斜树(只有左子树),右斜树(只有右子树);

l  满二叉树:所有的结点都有左右子树,所有叶子结点都在同一层;

l  完全二叉树:对于一棵具有n个结点的二叉树按层序编号,编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i 的结点在二叉树中位置完全相同;(完全二叉树不一定是满的);

l  二叉树的特点:

n  叶子结点只能出现在最下两层;

n  最下层的叶子结点一定集中在左部连续位置;

n  倒数二层,若有叶子结点,一定都在右侧连续位置;

n  如果结点度为1,则该结点只有左孩子;(此种情况应是最后一个结点吧?)

n  同样点数的二叉树,完全二叉树的深度最小;

 

6.6 二叉树的性质

l  在二叉树的第i层上至多有2i-1个结点;

l  深度为k的二叉树至多有2k-1个结点(k>=1);

l  对于任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1;设n1为度是1的结点数,那么T的结点总数n=n0+n1+n2

l  具有n个结点的完全二叉树的深度为[log2n]+1;([x]表示不大于x的最大整数)

l  对一棵有n个结点的完全二叉树的结点按层次编号,对于任一结点(1<=i<=n)有:

n  如果i=1;则结点i是二叉树的根;如果i>1,则其双亲是结点[i/2];

n  如果2i>n,则结点i无左孩子(为叶子结点),否则其左孩子是结点2i;

n  如果2i+1>n,则结点i无右孩子,否则其右孩子是结点2i+1;

 

6.7 二叉树的存储结构

6.7.1 二叉树的顺序存储结构(适用性不强)

用一维数组存储二叉树中的结点,结点的存储位置(数组的下标)要能体现结点间的逻辑关系;

l  完全二叉树:结点按照层序顺序依次存入到数组中;数组下标对应结点层序顺序的编号;

l  一般二叉树:不存在的结点设置为^,但会造成空间的浪费,所以顺序存储结构一般只用于完全二叉树;

6.7.2 二叉链表

二叉树每个结点最多有两个孩子,所以为他设计一个数据域和两个指针域;

[leftChild][data] [rightChild]

还可以增加一个指向其双亲的指针域,称之为三叉链表;

 

6.8 遍历二叉树(traversing binary tree)

6.8.1 二叉树遍历原理

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

6.8.2 二叉树遍历方法(若树为空,则空操作返回)

l  前序遍历:从根结点开始,先左子树后右子树;

l  中序遍历:从最左边的叶子或结点开始,先遍历根每个结点的左子树,然后访问根结点,最后遍历右子树,到最右边的叶子或结点终止;

l  后序遍历:从左到右,每棵子树都先叶后结;最后一个访问根结点;

l  层序遍历:从上到下,从左到右按照层序依次遍历;

6.8.3前序遍历算法(根结点在前)

利用递归,先打印结点数据,然后依次递归调用自身函数遍历左子树,右子树;

6.8.4 中序遍历算法(根结点在中)

和前序遍历只是顺序上的不同;

先递归调用自身函数遍历左子树,然后打印结点数据,再遍历右子树;

6.8.5 后序遍历算法(根节点在后)

顺序不同,先递归调用自身函数遍历左子树,再遍历右子树,然后打印结点数据;

6.8.6 推导遍历结果

二叉树遍历性质:

已知前序、中序或后序、中序,可以得到唯一确定的一棵二叉树,但已知前序和后序不可以;

6.9二叉树的建立

和遍历二叉树一样,也是利用递归的原理,只不过是把遍历时打印结点的地方改成生成结点;

6.10线索二叉树

6.10.1 线索二叉树原理

一方面:对于一个有n个结点的二叉链表,一共有2n个指针域,n-1条分支线数,也就是说,存在2n-(n-1)=n+1个空指针域,浪费内存资源;

另一方面:在二叉链表中,如果不遍历的话,不能知道某个节点的前驱和后继是谁,只知道每个结点的左右孩子的地址;

所以,可以考虑利用这些空地址,存放指向结点子某种遍历次序下的前驱和后继结点的地址;

把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)

         把空指针域中的rightChild指向它的后继结点,leftChild指向它的前驱结点;这样就相当于把一棵二叉树转变成了一个双向链表;

         把对二叉树以某种次序遍历使其变为线索二叉树的过程称作线索化

         如何区分rightChild/leftChild指向的是左右孩子还是前驱/后继?在每个结点增设两个标志域leftTag/rightTag(boolean型,取值为0(指向孩子)或1(指向前驱/后继));

[leftChild][leftTag] [data] [rightTag] [rightChild]

6.10.2线索二叉树结构实现

实质就是将二叉链表中的空指针改为指向前驱或后继的线索,由于前驱和后继的信息只有在遍历该二叉树时才能得到,所以线索化的过程就是在遍历的过程中修改空指针的过程

和双向链表结构一样,在二叉树线索链表上添加一个头结点,令其leftChild域的指针指向二叉树的根结点,其rightChild域的指针指向中序遍历时访问的最后一个结点;反之,令二叉树的中序序列中的第一个结点中的leftChild指针域和最后一个结点的rightChild指针域均指向头结点;这样的好处就是:即可以从第一个结点顺着后续进行遍历,也可以从最后一个结点起顺着前驱进行遍历

6.11树、森林与二叉树的转换

6.11.1 树转换为二叉树

l  加线:在所有兄弟结点之间加一条线;

l  去线:对树中每个结点,只保留它与第一个孩子结点的连线;

l  层次调整:以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使层次分明(注意:第一个孩子是二叉树结点的左孩子,其兄弟转换过来作为它的右孩子);

6.11.2 森林转换为二叉树

l  把每棵树转换为二叉树;

l  第一棵二叉树不动,从第二棵二叉树开始,后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子;

6.11.3 二叉树转换为树

是树转换为二叉树的逆过程;

l  加线:某结点的左孩子的n个右孩子结点都作为此结点的孩子;

l  去线:删除原二叉树中所有结点与其右孩子结点的连线;

l  层次调整;

6.11.4 二叉树转换为森林

先判断二叉树能否转换成森林:即看这棵二叉树的根结点有没有右孩子;

l  从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,连线删除,以此类推;

l  再将分离后的二叉树转换为树;

6.11.5 树与森林的遍历

l  树的遍历:

n  第一种-先根遍历:即先访问树的根结点,然后依次先根遍历每棵子树;

n  第二种-后跟遍历:即先依次后根遍历每棵子树,在访问根结点;

l  森林的遍历:

n  第一种-前序遍历:先访问森林中第一棵树的根结点,然后再依次先根遍历根的每棵子树,再依次用同样的方式遍历剩余的树;

n  第二种-后序遍历:先访问森林中第一棵树,以后根遍历的方式遍历每棵子树,然后访问根结点,再依次用同样的方式遍历剩余的树;

6.12 赫夫曼树及其应用

6.12.1 赫夫曼树

做数据分层级统计的时候,令根结点做概率最大的数据判断,减少判断次数;

6.12.2 赫夫曼树的定义与原理

中树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称为路径长度

         树的路径长度就是从树根到每一结点的路径长度之和;

         树的带权路径长度为树中所有叶子结点的带权路径长度之和(叶子结点带权Wk,每个叶子路径长度为Lk);

         其中带全路径长度WPL最小的二叉树为赫夫曼树

         如何构造赫夫曼树:把最小权值树两棵树作为左右子树构造一棵新的二叉树,所得新树的根结点的权值为左右子树的权值之和;以此类推;

6.12.3 赫夫曼编码

利用赫夫曼树原理,左分支代表0,右分之代表1;从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,即赫夫曼编码;

虽然长短不一,但保证了任一字符的编码都不是另一个字符编码的前缀;

原创粉丝点击