数据结构与算法分析~笔记6树和二叉树

树形结构是一类重要的非线性结构。树形结构是结点之间有分支,并且具有层次关系的结构。

6.1 树

(Tree)是n(n≥0)个结点的有限集。树的递归定义如下:当n=0时,T称为空树;当n>0时,T是非空树。在一棵非空树中:
(1)有且仅有一个特定的结点,它只有后继结点,没有前驱结点,这个结点称为(Root);
(2)当n>1时,除根以外的其余结点分为m(m>0)个互不相交的有限集合T1,T2,…Tm其中每一个集合本身又是一棵树,并且称之为根的子树(SubTree)。根据上述定义,树T的定义可以记作:
在这里插入图片描述
其中,root表示T的根,T1,T2,…Tm表示T的m棵子树。
例如,图(a)所示是一棵空树,一个结点都没有。图(b)所示是只有一个结点的树,它的子树为空,且该结点为根结点。图(c)所示是一棵有16个结点的树,其中A是根结点,它一般画在树的最顶部。其余结点分成3个互不相交的子集:T1={B,E,F,K,O,P},T2={C,G},T3={D,H,I,J,L,M,N};T1、T2和T3都是A的子树,且本身也是一棵树。
在这里插入图片描述
结点(Node):树中的每个元素对应一个结点。结点包含数据项及若干指向其他结点的分支。
结点的度(Degree):是结点所拥有的子树的个数。例如在上图(c)所示的树中,根结点A的度为3,结点B的度为2。
树的度(Degree):树中所有结点的度的最大值。
叶子结点(Deaf):即度为0的结点,又称为终端结点,叶子结点简称为叶子。
分支结点(Branch):即度不为0的结点,又称为非终端结点,分支结点简称为分支。
孩子结点(Child):若结点X有子树,则子树的根结点即为结点X的孩子结点,孩子结点简称孩子。例如在上图(c)所示的树中,结点A有3个孩子(B,C,D),结点B有2个孩子(E,F),结点L没有孩子。
双亲结点(Parent):若结点X有孩子,则X即为孩子的双亲结点,双亲结点简称双亲。
兄弟结点(Sibling):同一双亲的孩子结点间互称为兄弟结点,兄弟结点简称兄弟。例如在上图(c)所示的树中,结点B,C,D互为兄弟,E,F也互为兄弟,但F,G,H不是兄弟。
堂兄弟结点(Cousin):结点在树中的层次相同,但双亲不同的结点称为堂兄弟结点,堂兄弟结点简称堂兄弟。
结点的层次:根结点的层次为1,根结点的孩子的层次为2,根结点的孩子的孩子的层次为3,依次类推。
祖先结点(Ancestor):从根结点到结点X所经分支上的所有结点,都称为X的祖先结点,祖先结点简称祖先。
子孙结点(Descendant):结点X的孩子,以及这些孩子的孩子都是X的子孙结点,子孙结点简称子孙。
树的深度(Depth):树中距离根最远的结点所处的层次既为树的深度。空树的深度为0,只有一个根结点的树的深度为1。
树的高度(Height):有的书中将树的高度等同于树的深度,本书则从下向上定义高度。叶子结点的高度为1,非叶子结点的高度等于它的孩子结点高度的最大值加一,这样可定义树的高度等于根结点的高度。高度与深度计算的方向不同,但数值相等。
路径(Path):从树的双亲结点移动到其孩子结点和其他子孙结点所经过的路线叫做路径;路径上经过的边的个数称为路径长度(Path Length)。显然,在树中路径是唯一的。
有序树(Ordered Tree):若树中各个结点的各棵子树从左到右都是有次序的树,则称该树为有序树。
无序树(Unordered Tree):若树中各个结点的各棵子树之间不存在确定的次序关系,可以相互交换位置,则称该树为无序树。
森林(Forest):m(m≥0)棵互不相交的树的集合称之为森林。在自然界,树与森林是两个不同的概念,但在数据结构中,它们之间的差别很小。删去一棵非空树的根结点和该根结点的分支,则树就变成了森林(不排除空的森林);反之,若增加一个根结点和若干分支,让森林中每一棵树的根结点都变成它的孩子,森林和新增加的根结点及分支,就组成了一棵树。
树的性质
性质1 树中的结点个数等于树中所有结点的度数之和再加1。
性质2 度为m的树中第i层上至多有mi-1个结点(i≥1)。
树的存储要求既要存储结点的数据元素本身,又要存储结点之间的逻辑关系。由于树中各个结点的度可能不同,因此在存储过程中常遇到以下两类问题。
(1)根据树的度分配结点所占空间,虽然可保证结点同构,但会造成存储空间的浪费;
(2)根据各个结点的度来分配结点所占空间,虽然节省了存储空间,但造成整棵树的结构不统一,后续计算复杂度增高。
因此,虽然树的存储方式有很多种,既可以采用顺序存储结构,也可以采用链式存储结构。
由树的定义可知,除根结点之外,树中的每个结点都有唯一的双亲,根据这一特点,可以用一组连续的存储空间,即一维数组来存储树中的各个结点,数组中的一个数据元素表示树中的一个结点,数据元素为结构体类型,其中包括结点本身的信息以及其双亲结点在数组中的位置信息,树的这种存储方法称为双亲表示法(Parent Express)。这种存储结构不能反映各兄弟之间的关系,下图所示是一棵树及使用双亲表示法表示该树的存储结构:
在这里插入图片描述
树的链式存储结构:
(1)孩子表示法。
① 多重链表表示法。
由于树中每个结点都有零个或多个孩子,可以令每个结点包括一个结点信息域和多个指针域,每个指针域指向该结点的一个孩子,通过各个指针域反映出树中各结点之间的逻辑关系。在这种表示法中,树中每个结点都有多个指针域,形成了多条链表,所以这种方法又称为多重链表法
一棵树中,各结点的度数各异,因此结点的指针域个数设置有两种方法,与之对应的链表中结点有两种结构表示:
• 每个结点的指针域的个数等于树的度数,结点结构如图(a)所示;结点结构中的d为树的度,由于树中很多结点的度小于d,所以链表中有许多空链域,浪费了一部分存储空间。适用于各结点的度数相差不大的情况。
• 每个结点的指针域的个数等于该结点的度数,结点结构如图(b)所示。d′表示该结点的度,degree域的值同d′。这种方法在一定程度上节约了存储空间,但因为各种操作都不容易实现,所以很少采用。
在这里插入图片描述
树的多重链表表示存储示意图:
在这里插入图片描述
② 孩子链表表示法。
孩子链表表示法存储单元的主体是一个与结点个数一样大小的一维数组,数组的每一个元素由两个域组成,一个域用来存放结点自身的数据信息,另一个域用来存放指针,该指针指向由该结点孩子组成的单链表的表头。单链表的结点结构也由两个域组成,一个存放孩子结点在一维数组中的序号,另一个是指针域,指向下一个孩子。此方法适用于对孩子操作较多的应用。下图所示是上图(a)所示树的孩子链表表示存储示意图:
在这里插入图片描述
(2)双亲孩子表示法。
双亲孩子表示法是将双亲表示法与孩子表示法相结合的存储方法。它仍将各结点的孩子结点分别组成单链表,同时用一维数组顺序存储树中的各结点,一维数组中的数据元素除了包括结点本身的信息和该结点的孩子结点链表的头指针之外,还增设一个域,以存储该结点的双亲结点在数组中的位置。下图所示是上图(a)所示树的双亲孩子表示存储示意图:
在这里插入图片描述
(3)孩子兄弟表示法。
孩子兄弟表示法又称为二叉链表表示法或二叉树表示法。即以二叉链表作为树的存储结构,链表中结点的两个链域分别指向该结点的第一个孩子结点和下一个兄弟结点,分别命名为firsrchild域和nextsibling域。其结点结构如下图所示:
在这里插入图片描述
遍历(Traverse)是树的基本操作。树的遍历是指从根结点出发,按照某种次序访问树中所有结点,使得每个结点被访问一次且仅被访问一次。
由树的定义可知,一棵树由根结点和m棵子树构成。因此,只要依次遍历根结点和m棵子树,就可以遍历整棵树。树的遍历通常有先根遍历、后根遍历和层次遍历3种方式。
给定树T,如果T=Φ,则遍历结束;否则T={root,T1,T2,…,Tm},则树的先根遍历、后根遍历和层次遍历方法定义如下。

  1. 先根遍历
    树的先根遍历也称为先序遍历,其操作可递归定义如下。若树T=Φ,则遍历结束;否则:
    (1)访问树的根结点root;
    (2)按照从左到右的顺序依次先根遍历根的第一棵子树T1,第二棵子树T2,…,直到最后一棵子树Tm。
  2. 后根遍历
    树的后根遍历也称为后序遍历,其操作可递归定义如下。若树T=Φ,则遍历结束;否则:
    (1)按照从左到右的顺序依次后根遍历根的第一棵子树T1,第二棵子树T2,…,直到最后一棵子树Tm。
    (2)访问树的根结点root。
  3. 层次遍历
    树的层次遍历是从根结点开始,按从上到下,从左到右的顺序依次访问树中的每一个结点。即首先访问第1层的结点,再按照自左向右顺序访问第2层的结点,直到所有层的结点都访问完为止。

6.2 森林

森林是m(m≥0)棵互不相交的树的集合。由于每棵树都有一个根结点,因此森林可以有多个根结点。森林的存储结构与树类似,也可采用双亲表示法、双亲孩子表示法和孩子兄弟表示法。
森林的存储结构

  1. 森林的顺序存储结构
    森林的双亲表示法就是一种顺序存储结构。与树的双亲表示法类似。下图所示是一个森林及其使用双亲表示法表示的存储结构示意图:
    在这里插入图片描述
    用parent域的值为-1表示该结点无双亲,即该结点是根结点;其他值则表示该结点的双亲在一维数组中存储的位置,即一维数组中的下标。森林的双亲表示法与树的区别在于,森林对应的一维数组中值为-1的parent域可能不止一个。
  2. 森林的链式存储结构
    (1)双亲孩子表示法
    与树的双亲孩子表示法相同,森林的双亲孩子表示法,仍将各结点的孩子结点分别组成单链表,同时用一维数组顺序存储树中的各结点,一维数组元素除了包括结点本身的信息和该结点的孩子结点链表的头指针之外,还增设一个域,以存储该结点的双亲结点在数组中的位置。下图所示是上图(a)所示森林的双亲孩子表示存储示意图:
    在这里插入图片描述
    (2)孩子兄弟表示法与树的孩子兄弟表示法相同,森林的孩子兄弟表示法,仍以二叉链表作为存储结构,链表中结点的两个链域分别指向该结点的第一个孩子结点和下一个兄弟结点,分别命名为firsrchild域和nextsibling域。不同的是,森林中的树根结点互为兄弟结点,这样只要知道第一棵树的根,根据它的nextsibling域即可得到第二棵树的根,再根据第二棵树根的nextsibling域进一步可知第三棵树的根,依次类推。下图所示是上图(a)所示森林的孩子兄弟(二叉链表)表示的存储结构示意图:
    在这里插入图片描述
    按照森林和树相互递归的定义,可以推出森林的两种遍历方法。
  3. 先序遍历森林
    若森林非空,则可按下述规则遍历之:
    (1)访问森林中第一棵树的根结点;
    (2)先序遍历第一棵树中根结点的子树森林;
    (3)先序遍历除去第一棵树之后剩余的树构成的森林。
  4. 中序遍历森林
    若森林非空,则可按下述规则遍历之:
    (1)中序遍历森林中第一棵树的根结点的子树森林;
    (2)访问第一棵树的根结点;
    (3)中序遍历除去第一棵树之后剩余的树构成的森林。

6.3 二叉树

二叉树(Binary Tree)是另外一种树形结构,它的特点是每个结点至多有两棵子树,分别称为左子树右子树。即二叉树中不存在度大于2的结点,并且二叉树的子树有左右之分,其次序不能随意颠倒。任何树和森林都可以转换为二叉树。
二叉树的定义:一棵二叉树T是n(n≥0)个结点的有限集合。当n=0时,它是一棵空二叉树;当n>0时,它由一个根结点和两棵分别称为左子树和右子树且互不相交的二叉树组成。二叉树的定义也是一个递归定义,许多基于二叉树的算法都利用了这个递归的特性。二叉树的递归定义用公式表述如下:
在这里插入图片描述
其中,root表示T的根,TL表示T的左子树,TR表示T的右子树,TL、TR仍是二叉树。
二叉树中即使只有一棵子树也要进行区分,说明它是左子树,还是右子树,这是二叉树与树的最主要的差别。因此,“二叉树就是结点度为2的树”的说法是错误的。二叉树一共有5种基本形态,如下图所示:
在这里插入图片描述
图(a)表示一棵空二叉树。图(b)是只有根结点的二叉树,根的左子树和右子树都是空的。图(c)是根的右子树为空的二叉树。图(d)是根的左子树为空的二叉树。图(e)是根的两棵子树都不为空的二叉树。任意一棵二叉树肯定是这5种基本形态中的某一种。
二叉树具有如下性质:
性质1 在二叉树的第i(i≥1)层上至多有2的i-1次方个结点。
性质2 深度为k(k≥1)的二叉树至多有2的k次方减1个结点。
性质3 对任何一棵非空二叉树,如果其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1。
其他一些性质是有关某些特殊二叉树的。为此,先定义两种特殊的二叉树:满二叉树和完全二叉树。
满二叉树(Full Binary Tree):深度为k且有2k-1个结点的二叉树称为满二叉树。在满二叉树中,每一层结点都达到了最大个数。
完全二叉树(Complete Binary Tree):如果一棵深度为k且具有n个结点的二叉树,它的每一个结点都与深度为k的满二叉树中顺序编号为1~n的结点一一对应,则称这棵二叉树为完全二叉树。
其特点是:从第1层到第k-1层的所有各层的结点数都是满的,只有第k层或是满的,或是从右向左连续缺若干结点,但是第k层不能为空。
在这里插入图片描述
根据满二叉树和完全二叉树定义可知:
(1)满二叉树是完全二叉树的特例,满二叉树一定是一棵完全二叉树。而完全二叉树不一定是一棵满二叉树。
(2)满二叉树的叶子结点全都在最底层,而完全二叉树的叶子结点可以分布在最下面两层。
性质4 具有n(n>0)个结点的完全二叉树的深度为[log2n]+1。
性质5 如果将一棵有n个结点的完全二叉树按照自顶向下,同一层自左向右的顺序连续给结点编号1,2,3,…,n,然后按此结点编号将树中各结点顺序地存放于一个一维数组中,并简称编号为i的结点为结点i(1≤i≤n),参考下图(b),则有以下结论:
在这里插入图片描述

(1)若i=1,则结点i为根,无双亲结点;若i>1,则结点i的双亲结点为结点[i/2]。
(2)若2i≤n,则结点i的左孩子为结点2i;否则结点i无左孩子。
(3)若2i+1≤n,则结点i的右孩子为结点2i+1;否则结点i无右孩子。

二叉树的存储结构主要有两种:顺序(数组)存储和链式存储。
(1)完全二叉树的顺序存储表示
按照顺序存储的定义,用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素,存储时只保存各结点的值。由二叉树性质5可知,对于完全二叉树,若已知结点的编号,则可以推算出它的双亲和孩子结点的编号,所以只需将完全二叉树的各结点按照编号的次序1~n依次存储到数组对应下标为0~n-1的位置,就很容易根据结点在数组中存储的位置,计算出它的双亲结点和孩子结点的存储位置。如下图(b)为图(a)所示完全二叉树的顺序存储结构示意图:
在这里插入图片描述
(2)一般二叉树的顺序存储表示
设有一棵一般的二叉树,需要将它放在一个一维数组中。为了实现对某个结点的查找和定位,也需仿照完全二叉树那样,对二叉树的结点进行顺序编号。在编号时,增加一些并不存在的空结点并按顺序对其进行编号处理,使之成为一棵与原二叉树高度相同的完全二叉树的形式,然后再用一维数组进行顺序存储。
这种存储方式能反映二叉树结点之间的相互关系,由其存储位置找到它的双亲结点、孩子、兄弟结点的位置。但显然这种存储需增加许多空结点的存储空间才能将一棵一般二叉树改造为完全二叉树,这样必然会造成大量空间浪费,因此这种二叉树不适合进行顺序存储。
(3)二叉树的链式存储结构
在实际应用中,二叉树一般多采用链式存储结构。根据二叉树的定义,二叉树的每个结点可以有两个分支,分别指向结点的左、右子树。因此,二叉树的结点至少应当包括3个域,分别存放结点的数据信息data、左孩子结点指针lchild和右孩子结点指针rchild。
其中:data:数据域,存放该结点自身的数据信息。lchild:左指针域,存放指向左孩子的指针,当左孩子不存在时为空指针。rchild:右指针域,存放指向右孩子的指针,当右孩子不存在时为空指针。
如下图(a)所示,这种存储结构称为二叉链表(Binary Linked List)。为了便于查找到任一结点的双亲结点,可以在结点结构中再增加一个指向双亲结点的指针域,这样的存储结构称为三叉链表(Trifurcate Linked List),如下图(b)所示。
在这里插入图片描述
整个二叉树的链表有一个表头指针,它指向二叉树的根结点。
根据性质3可知,在含有n个结点的二叉链表中有n+1个空链指针域,这是因为在所有结点的2n个链指针域中,只有n-1个存有分支信息的缘故。三叉链表则有n+2个空链指针域。
遍历二叉树:指遵从某种顺序,顺着某一条搜索路径访问二叉树中的各个结点,使得每个结点均被访问一次,而且仅被访问一次。
根据二叉树的结构特征,遍历二叉树可以有3种搜索路径:先上后下的按层次遍历、先左(子树)后右(子树)的遍历、先右(子树)后左(子树)的遍历。
设访问根结点记作D,遍历根的左子树记为L,遍历根的右子树记为R,则可能遍历的次序有:DLR、DRL、LDR、LRD、RDL、RLD及层次遍历。若规定先左后右,则只剩下4种遍历方式:DLR、LDR、LRD及层次遍历。依据根结点被遍历的次序,通常称DLR、LDR和LRD3种遍历为先序遍历、中序遍历和后序遍历。
二叉树的先序遍历定义如下:如果二叉树为空,则遍历结束;否则:① 访问根结点(D);② 先序遍历左子树(L);③ 先序遍历右子树(R)。先序遍历也称为前序遍历,就是按照“根—左子树—右子树”的次序遍历二叉树。
二叉树的中序遍历定义如下:如果二叉树为空,则遍历结束;否则:① 中序遍历左子树(L);② 访问根结点(D);③ 中序遍历右子树(R)。中序遍历就是按照“左子树—根—右子树”的次序遍历二叉树。
二叉树的后序遍历定义如下:如果二叉树为空,则遍历结束;否则:① 后序遍历左子树(L);② 后序遍历右子树(R)。③ 访问根结点(D);后序遍历就是按照“左子树—右子树—根”的次序遍历二叉树。
二叉树的层次遍历就是按照二叉树的层次,从上到下、从左到右的次序访问各结点。遍历的示例如下图所示:
在这里插入图片描述
二叉树是表达式a+(b-c)*d-e/f的存储形式:
在这里插入图片描述
从表达式上看,以上3个序列(6-1)、(6-2)和(6-3)恰好为表达式的前缀表示(波兰式)、中缀表示和后缀表示(逆波兰式)。
(1)递归算法
二叉树的先序、中序和后序遍历的定义均采用了实现简单的递归方式进行描述。
(2)非递归算法
递归算法虽然简洁,但执行效率不高。因此,有时需要把递归算法转化为非递归算法。对于二叉树先序遍历的非递归实现,可以仿照递归算法的执行过程中工作栈的状态变化得到。
为了把一个递归过程改为一个非递归过程,一般需要利用一个工作栈,记录遍历时的回退路径。在改写时,可以通过一个实例分析一个递归算法的执行过程,观察栈的变化,直接写出它的非递归算法。

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

就逻辑结构而言,任何一棵树是一个二元组T=(root,F),其中:root是数据元素,称为树的根结点;F是m(m≥0)棵树组成的森林,F=(T1,T2,…,Tm),其中,Ti=(ri,Fi)称作根root的第i棵子树;当m≠0时,在树根和其子树森林之间存在如下关系:RF={<root,ri>|i=1,2,…,m;m>0}这个定义将有助于得到树、森林与二叉树之间转换的递归定义。
树与二叉树的转换
由于二叉树和树都可以用二叉链表作为存储结构,则以二叉链表作为中间形态可导出树与二叉树之间的一个对应关系。也就是说,给定一棵树,可以找到唯一的一棵二叉树与之对应。从物理结构来看,它们的二叉链表是相同的,只是解释不同而已。
一棵树采用孩子兄弟表示法所建立的存储结构与它所对应的二叉树的二叉链表存储结构是完全相同的。
如下图(a)所示的一棵树,根结点A有B、C、D三个孩子,可以认为结点B是A的第一个孩子结点,结点C是A的第二个孩子结点,结点D是A的第三个孩子结点。将一棵树装换成二叉树的方法如下:
(1)加线:将树中所有相邻兄弟之间加一条连线,如图(b)所示。
(2)抹线:对树中的每个结点,只保留它与第一个孩子结点之间的连线,删除它与其他孩子结点之间的连线,如图(c)所示。
(3)旋转:以树的根结点为轴心,将整棵树顺时针旋转45°,使之成为一棵层次分明的二叉树,如图(d)所示。
在这里插入图片描述

从上面的转换可以看出,在二叉树中,左子树上的各结点在原来的树中与其双亲结点是父子关系,而右子树上的各结点在原来的树中与其双亲结点是兄弟关系。由于根结点没有兄弟,所以变换后的二叉树的根结点的右子树必为空。同理,一棵二叉树若要转换成一棵树,则右子树必定为空,下图所示为一棵二叉树转换成树的过程示意图,其转换过程恰好是树转换成二叉树的逆过程。
在这里插入图片描述
森林与二叉树的转换
森林转换成二叉树的方法如下:
(1)将森林中的每棵树转换成相应的二叉树。
(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有的二叉树连起来之后,此时所得到的二叉树就是由森林转换得到的二叉树。
这一方法可形式化描述为:若F=(T1,T2,…,Tm)是森林,则可按如下规则转换成一棵二叉树B=(root,TL,TR)。
(1)若F为空,即m=0,则B为空树。
(2)若F非空,即m≠0,则B的根root即为森林中的第一棵树的根Root(T1);B的左子树TL是由T1中根结点的子树森林F1=(T11,T12,…,T1m)转换而成的二叉树;其右子树TR是由森林F’=(T2,T3,…,Tm)转换而成的二叉树。下图给出了森林及其转换为二叉树的过程。
在这里插入图片描述
树和森林都可以转换成二叉树,二者不同的是树转换成的二叉树,其根结点右子树为空,而森林转换后的二叉树,其根结点右子树不为空。显然这一过程是可逆的,即可以依据二叉树的根结点有无右子树,将一棵二叉树还原为树或森林,具体方法如下:
(1)若某结点是双亲的左孩子,则把该结点的右孩子、右孩子的右孩子……都与该结点的双亲结点用线连起来。
(2)删去原二叉树中所有的双亲结点与其右孩子结点的连线。
(3)整理由(1)、(2)两步所得到树或森林,使之结构层次分明。
这一方法可形式化描述为:若B=(root,TL,TR)是一棵二叉树,则可按如下规则转换成森林F=(T1,T2,…,Tm)。
(1)若B为空,则F为空。
(2)若B非空,则森林中第一棵树T1的根Root(T1)即为B的根root;T1中根结点的子树森林F1是由B的左子树TL转换而成的森林;F中除T1之外其余树组成的森林F’=(T2,T3,…,Tm)是由B的右子树TR转换而成的森林。
下图给出了一棵二叉树还原为森林的过程:
在这里插入图片描述
由森林与二叉树之间转换的规则可知,当森林转换为二叉树时,其第一棵树的子树森林转换成左子树,剩余树的森林转换成右子树,则上述二叉树的先序和中序遍历即为其对应的森林的先序和中序遍历。即相对应的二叉树和森林分别进行先序和中序遍历,可得到相同的序列。

6.5 堆

设有n个元素的序列{k1,k2,…,kn},当且仅当满足下述关系之一时,称之为堆(Heap)。
在这里插入图片描述
若以一维数组存储堆,则堆对应为一棵完全二叉树,且所有非叶子结点的值均不大于(或不小于)其子女的值,根结点的值是最小(或最大)的。因此,也可以这样来定义堆。堆是具有下列其中某一条性质的完全二叉树:
(1)每个结点的值都小于或等于其左右孩子结点的值,称为小根堆或小顶堆;
(2)每个结点的值都大于或等于其左右孩子结点的值,称为大根堆或大顶堆。
堆的示例图如下:
在这里插入图片描述
堆对应的是完全二叉树,因此其类定义和基本操作可参考二叉树的相关实现。

6.6 哈夫曼树和哈夫曼编码

哈弗曼树是利用哈夫曼编码构造的一种特殊结构的二叉树,二叉树实际应用的一种。
哈夫曼树,又称为最优二叉树,是指一类带权路径长度最小的二叉树。哈夫曼树定义中涉及的术语含义如下。
结点的权:对结点赋予的一个有着某种意义的数值。
结点的带权路径长度:从树根结点到该结点之间的路径长度与该结点权值的乘积。
叶子结点:树中度为0的结点,也称为终端结点。
树的带权路径长度:树中所有叶子结点的带权路径长度之和。
树的带权路径长度WPL可记为:
在这里插入图片描述

其中Wk为第k个叶子结点的权值,Lk为第k个叶结点的路径长度。下图所示为树的带权路径长度的计算示意图:
在这里插入图片描述
根据一组具有确定权值的叶子结点,可以构造出不同的带权二叉树,其中带权路径长度最小的二叉树称为哈夫曼树(Huffman Tree),又称为最优二叉树
下图给出了其中5个不同形状的二叉树。其中图(b)所示的二叉树就是一棵哈夫曼树。
在这里插入图片描述
根据哈夫曼树的定义,一个二叉树要使其带权路径长度最小,必须让权值大的结点靠近根结点,而权值小的结点远离根结点。据此,哈夫曼树的构造算法如下:
(1)根据给定n个权值{w1,w2,…,wn}构造n棵二叉树的集合F=(T1,T2,…,Tn),其中每棵二叉树Ti中只有一个权值为wi的根结点,其左、右子树均空。
(2)在F中选取两棵根结点的权值最小的树分别作为左、右子树构造一棵新的二叉树,且将新的二叉树的根结点的权值为置其左、右子树上根结点的权值之和。
(3)在F中删除作为左、右子树的两棵二叉树,同时将新得到的二叉树加入到F中。
(4)重复(2)和(3),直到F只含一棵树为止,这棵树便是哈夫曼树。
下图给出了叶子结点权值集合为W={5,29,7,8,14,23,3,11}的哈夫曼树的构造过程,其带权路径长度为100。哈夫曼树的形状可以不同,但不同形状的哈夫曼树的带权路径长度一定相同,且是所有带权路径长度的最小值。
在这里插入图片描述
在数据通信中,经常需要将传送的文字转换成由二进制字符0、1组成的字符串(也称为编码)。例如,假设要传送的电文为ABACCDA,电文中只含有A、B、C、D 4种字符,若这4种字符采用表6.1所示的等长编码,则电文的代码为000010000100100111000,长度为21。
在这里插入图片描述
如果在编码时考虑字符出现的频率,让出现频率高的字符采用尽可能短的编码,出现频率低的字符采用稍长的编码,构造一种不等长编码,则电文的代码总长度就可能更短。如当字符A、B、C、D采用表6.3所示的编码时,电文为ABACCDA的代码为1000101010011,长度为13。
在这里插入图片描述

为设计电文总长最短的编码方式,可通过构造以字符使用频率作为权值的哈夫曼树。具体做法如下:设需要编码的字符集合为{d1,d2,…,dn},它们在电文中出现的次数或频率集合为{w1,w2,…,wn},以d1,d2,…,dn作为叶子结点,w1,w2,…,wn}作为它们的权值,构造一棵哈夫曼树,规定哈夫曼树中左分支代表0,右分支代表1,则从根结点到每个叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,称之为哈夫曼编码
在编码树中,树的带权路径长度是指各个字符的码长与其出现次数的乘积之和,即电文的代码总长,所以采用哈夫曼树构造的编码是一种能使电文代码总长最短的不等长编码。在不等长编码选择上,必须使任何一个字符的编码都不是其他字符编码的前缀,以保证译码的唯一性。
哈夫曼编码的算法包括两个部分:
(1)构造哈夫曼树;
(2)在哈夫曼树上求叶子结点的编码。
第(2)步就是在已建立的哈夫曼树中,从叶子结点开始,沿结点的双亲链域回退到根结点,每回退一步,就经过一个分支,从而得到一位哈夫曼编码值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值