【浅学数据结构】数据结构之 “树”

树的定义

之前我们一直在谈论一对一的线性结构,其实还有一对多的数据结构,树就是这样的结构。
如图:
树数据结构
树状图是一种数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。当n=0时,称为空树。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

它具有以下的特点:

  1. 每个结点有零个或多个子结点;
  2. 没有父结点的结点称为根结点;
  3. 每一个非根结点有且只有一个父结点;
  4. 除了根结点外,每个子结点可以分为多个不相交的子树。

树相关的名词

节点的度:一个节点含有的子树的个数称为该节点的度;
叶节点或终端节点:度为0的节点称为叶节点;
非终端节点或分支节点:度不为0的节点;
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
兄弟节点:具有相同父节点的节点互称为兄弟节点;
树的度:一棵树中,最大的节点的度称为树的度;
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次;
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
节点的祖先:从根到该节点所经分支上的所有节点;
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
森林:由m(m>=0)棵互不相交的树的集合称为森林;

树的分类

无序树:树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树;
二叉树:每个节点最多含有两个子树的树称为二叉树;
完全二叉树
满二叉树
霍夫曼树
:带权路径最短的二叉树称为哈夫曼树或最优二叉树;

二叉树的定义

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

二叉树特点

  1. 只有一个根节点,每个节点最多有两棵子树,如没有节点则为空二叉树。
  2. 左子树和右子树是有顺序的,次序不能任意颠倒。
  3. 即使树种某节点只有一棵子树,也要区分它是左子树还是右子树。

特殊二叉树

1、斜树

根据名字,我们知道,树一定要斜才能叫斜树。但往那边斜,是左还是右呢?我们定义所有的节点都只有左子树的二叉树叫左斜树,所有的节点都是只有右子树的二叉树叫右斜树。这两种我们统称斜树。

2、满二叉树

顾名思义,就是所有分支节点都存在左子树和右子树,并且所有叶子都在同一层,这样的二叉树称满二叉树。

3、完全二叉树

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

首先从字面上要区分,“完全”和“满”的差异,满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树。其次完全二叉树的所有节点与同样深度的满二叉树。它们按层从左到右顺序编号相同的节点是一一对应的。

二叉树的性质

二叉树需要理解并记住的特性,以便我们更好的使用它。

性质1:在二叉树的第i层上至多有2i-1个节点(i ≥ 1)

结论推导过程如下:
第一层是根节点,只有一个,所有21-1=20=1。
第二层有2个,22-1=21=2。
第三层有4个,23-1=4。
第四层有8个,24-1=8。

性质2:深度(层数)为k的二叉树至多有2k-1个节点(k ≥ 1)

注意这里一定要看清楚,是22后再减去1。同性质1是有区别的。
结论推导过程如下:
如果有1层,至多有20-1=1个节点。
如果有2层,至多有22-1=3个节点。
如果有3层,至多有23-1=7个节点。
如果有4层,至多有24-1=15个节点。

性质3:对任何一棵树二叉树T,如果其终端节点数为n0,度为2的节点数为n2,则n0=n2+1

终端节点数其实就是叶子节点数,而一棵二叉树,除了叶子节点外,剩下的就是度为1或2的节点数了。我们设n1为度是1的节点数。则树T节点总数n=n0+n1+n2

性质4:具有n个节点的完全二叉树的深度为[log2]n+1([x]表示不大于x的最大整数)。

由满二叉树的定义我们可以知道,深度为k的满二叉树的结点树n一定是2k-1。因为这是最多的节点个数。那么对于n=2k-1倒推得到满二叉树的度数为k=log2(n+1),比如节点数为15的满二叉树,度为4。

性质5:如果对一棵有n个节点的完全二叉树(其深度为[log2n]+1)的节点按层序编号(从第1层到第【log2n】+1层,每层从左到右),对任一节点(1≤i≤n)有:

  1. 如果i=1,则节点i是二叉树的跟,无双亲;如果i>1,则其双亲是节点【i/2】。
  2. 如果2i>n,则节点i无左孩子(节点i为叶子节点);否则其左孩子是节点2i。
  3. 如果2i+1>n,则节点i无右孩子;否则其右孩子是节点2i+1。

二叉树的遍历

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

1. 前序遍历
规则是若二叉树空,则空操作返回,否则先访问根节点,然后前序遍历左子树,再前序遍历右子树。遍历顺序为:ABDGHCEIF。如下图:
前序遍历
2.中序遍历
规则是若树为空,则空操作返回,否则从根节点开始(注意并不是先访问根节点),中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。遍历顺序为:GDHBAEICF。如下图:
中序遍历
3.后序遍历
规则是若树为空,则空操作范围,否则从左到右先叶子后节点的方式遍历访问左右子树,最后是访问根节点。遍历顺序为:GHDBIEFCA。如下图:
后序遍历
4. 层序遍历
规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。遍历顺序为:ABCDEFGHI。如下图:
层序遍历
有人会问,研究这么多遍历干啥?
我们用图形的方式表现树的结构,应该说是非常直观和容易理解的,但是对计算机来说,它只有循环、判断等方式来处理,也就是说,它只会处理线性序列,而我们刚才提到的四种遍历方法,其实都是在把树中的节点变成某种意义的线性序列,这就给程序员的实现带来了好处。另外不同的遍历提供了对节点依次处理的不同方式,可以在遍历过程中对节点进行各种处理。

赫夫曼树(哈夫曼)及其应用

美国数学家赫夫曼,他在1952年发明了赫夫曼编码,为了纪念他的成绩,于是就把他在编码中用到的特殊二叉树称之为赫夫曼树,他的编码方法称为赫夫曼编码或叫哈夫曼。也就是说,我们现在介绍的知识全部来自近70年前这位伟大科学家的研究成果,而我们平时所用的压缩和解压技术也都是基于赫夫曼的研究之上发展而来的。

赫夫曼树定义与原理

赫夫曼大叔说,从树中一个节点到另一个节点之间的分支构成两个节点之间的路径,路径上的分支数目称做路径长度。
赫夫曼树,别名“哈夫曼树”、“最优树”以及“最优二叉树”。学习哈夫曼树之前,首先要了解几个名词。

哈夫曼树相关的几个名词

路径:在一棵树中,一个结点到另一个结点之间的通路,称为路径。图 1 中,从根结点到结点 a 之间的通路就是一条路径。

路径长度:在一条路径中,每经过一个结点,路径长度都要加 1 。例如在一棵树中,规定根结点所在层数为1层,那么从根结点到第 i 层结点的路径长度为 i - 1 。图 1 中从根结点到结点 c 的路径长度为 3。

结点的权:给每一个结点赋予一个新的数值,被称为这个结点的权。例如,图 1 中结点 a 的权为 7,结点 b 的权为 5。

结点的带权路径长度:指的是从根结点到该结点之间的路径长度与该结点的权的乘积。例如,图 1 中结点 b 的带权路径长度为 2 * 5 = 10 。

树的带权路径长度为树中所有叶子结点的带权路径长度之和。通常记作 “WPL” 。例如图 1 中所示的这颗树的带权路径长度为:
WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3
哈夫曼树

图1 哈夫曼树

什么是哈夫曼树

当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”,有时也叫“赫夫曼树”或者“哈夫曼树”。

在构建哈弗曼树时,要使树的带权路径长度最小,只需要遵循一个原则,那就是:权重越大的结点离树根越近。在图 1 中,因为结点 a 的权值最大,所以理应直接作为根结点的孩子结点。

构建哈夫曼树

对于给定的有各自权值的 n 个结点,构建哈夫曼树有一个行之有效的办法:

  1. 在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和;
  2. 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;
  3. 重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。
    哈夫曼树
    图 2 哈夫曼树的构建过程

图 2 中,(A)给定了四个结点a,b,c,d,权值分别为7,5,2,4;第一步如(B)所示,找出现有权值中最小的两个,2 和 4 ,相应的结点 c 和 d 构建一个新的二叉树,树根的权值为 2 + 4 = 6,同时将原有权值中的 2 和 4 删掉,将新的权值 6 加入;进入(C),重复之前的步骤。直到(D)中,所有的结点构建成了一个全新的二叉树,这就是哈夫曼树。

哈弗曼树中结点结构

构建哈夫曼树时,首先需要确定树中结点的构成。由于哈夫曼树的构建是从叶子结点开始,不断地构建新的父结点,直至树根,所以结点中应包含指向父结点的指针。但是在使用哈夫曼树时是从树根开始,根据需求遍历树中的结点,因此每个结点需要有指向其左孩子和右孩子的指针。

所以,哈夫曼树中结点构成用代码表示为:

//哈夫曼树结点结构
typedef struct 
{
  int weight;  //结点权重
  int parent, left, right;  //父结点、左孩子、右孩子在数组中的位置下标
}HTNode, *HuffmanTree;

哈弗曼树中的查找算法

构建哈夫曼树时,需要每次根据各个结点的权重值,筛选出其中值最小的两个结点,然后构建二叉树。

查找权重值最小的两个结点的思想是:从树组起始位置开始,首先找到两个无父结点的结点(说明还未使用其构建成树),然后和后续无父结点的结点依次做比较,有两种情况需要考虑:

  • 如果比两个结点中较小的那个还小,就保留这个结点,删除原来较大的结点;
  • 如果介于两个结点权重值之间,替换原来较大的结点;

实现代码:

//HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置
void Select(HuffmanTree HT, int end, int *s1, int *s2)
{
  int min1, min2;
  //遍历数组初始下标为 1
  int i = 1;
  //找到还没构建树的结点
  while(HT[i].parent != 0 && i <= end)
  {
    i++;
  }
  min1 = HT[i].weight;
  *s1 = i;
  i++;
  while(HT[i].parent != 0 && i <= end)
  {
    i++;
  }
  //对找到的两个结点比较大小,min2为大的,min1为小的
  if(HT[i].weight < min1)
  {
    min2 = min1;
    *s2 = *s1;
    min1 = HT[i].weight;
    *s1 = i;
  }
  else
  {
    min2 = HT[i].weight;
    *s2 = i;
  }
  //两个结点和后续的所有未构建成树的结点做比较
  for(int j=i+1; j <= end; j++)
  {
    //如果有父结点,直接跳过,进行下一个
    if(HT[j].parent != 0)
    {
      continue;
    }
    //如果比最小的还小,将min2=min1,min1赋值新的结点的下标
    if(HT[j].weight < min1)
    {
      min2 = min1;
      min1 = HT[j].weight;
      *s2 = *s1;
      *s1 = j;
    }
    //如果介于两者之间,min2赋值为新的结点的位置下标
    else if(HT[j].weight >= min1 && HT[j].weight < min2)
    {
      min2 = HT[j].weight;
      *s2 = j;
    }
  }
}

注意:s1和s2传入的是实参的地址,所以函数运行完成后,实参中存放的自然就是哈夫曼树中权重值最小的两个结点在数组中的位置。

构建哈弗曼树的代码实现

//HT为地址传递的存储哈夫曼树的数组,w为存储结点权重值的数组,n为结点个数
void CreateHuffmanTree(HuffmanTree *HT, int *w, int n)
{
  if(n <= 1) 
    return;   // 如果只有一个编码就相当于0
  int m = 2*n-1;   // 哈夫曼树总节点数,n就是叶子结点
  *HT = (HuffmanTree) malloc((m+1) * sizeof(HTNode));   // 0号位置不用
  HuffmanTree p = *HT;
  // 初始化哈夫曼树中的所有结点
  for(int i = 1; i <= n; i++)
  {
    (p+i)->weight = *(w+i-1);
    (p+i)->parent = 0;
    (p+i)->left = 0;
    (p+i)->right = 0;
  }
  //从树组的下标 n+1 开始初始化哈夫曼树中除叶子结点外的结点
  for(int i = n+1; i <= m; i++)
  {
    (p+i)->weight = 0;
    (p+i)->parent = 0;
    (p+i)->left = 0;
    (p+i)->right = 0;
  }
  //构建哈夫曼树
  for(int i = n+1; i <= m; i++)
  {
    int s1, s2;
    Select(*HT, i-1, &s1, &s2);
    (*HT)[s1].parent = (*HT)[s2].parent = i;
    (*HT)[i].left = s1;
    (*HT)[i].right = s2;
    (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
  }
}

哈夫曼编码

哈夫曼编码就是在哈夫曼树的基础上构建的,这种编码方式最大的优点就是用最少的字符包含最多的信息内容。

根据发送信息的内容,通过统计文本中相同字符的个数作为每个字符的权值,建立哈夫曼树。对于树中的每一个子树,统一规定其左孩子标记为 0 ,右孩子标记为 1 。这样,用到哪个字符时,从哈夫曼树的根结点开始,依次写出经过结点的标记,最终得到的就是该结点的哈夫曼编码。
文本中字符出现的次数越多,在哈夫曼树中的体现就是越接近树根。编码的长度越短。
哈夫曼编码
图 3 哈夫曼编码
如图 3 所示,字符 a 用到的次数最多,其次是字符 b 。字符 a 在哈夫曼编码是 0 ,字符 b 编码为 10 ,字符 c 的编码为 110 ,字符 d 的编码为 111 。

使用程序求哈夫曼编码有两种方法:

  1. 从叶子结点一直找到根结点,逆向记录途中经过的标记。例如,图 3 中字符 c 的哈夫曼编码从结点 c 开始一直找到根结点,结果为:0 1 1 ,所以字符 c 的哈夫曼编码为:1 1 0(逆序输出)。
  2. 从根结点出发,一直到叶子结点,记录途中经过的标记。例如,求图 3 中字符 c 的哈夫曼编码,就从根结点开始,依次为:1 1 0。

采用方法 1 的实现代码为:

//HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数
void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC, int n)
{
  *HC = (HuffmanCode) malloc((n+1) * sizeof(char *));
  char *cd = (char *)malloc(n*sizeof(char)); //存放结点哈夫曼编码的字符串数组
  cd[n-1] = '\0';//字符串结束符
  for(int i=1; i<=n; i++)
  {
    //从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放
    int start = n-1;
    //当前结点在数组中的位置
    int c = i;
    //当前结点的父结点在数组中的位置
    int j = HT[i].parent;
    // 一直寻找到根结点
    while(j != 0)
    {
      // 如果该结点是父结点的左孩子则对应路径编码为0,否则为右孩子编码为1
      if(HT[j].left == c)
        cd[--start] = '0';
      else
        cd[--start] = '1';
      //以父结点为孩子结点,继续朝树根的方向遍历
      c = j;
      j = HT[j].parent;
    }
    //跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码
    (*HC)[i] = (char *)malloc((n-start)*sizeof(char));
    strcpy((*HC)[i], &cd[start]);
  }
  //使用malloc申请的cd动态数组需要手动释放
  free(cd);
}

采用第二种算法的实现代码为:

//HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数
void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC, int n)
{
  *HC = (HuffmanCode) malloc((n+1) * sizeof(char *));
  int m = 2*n-1;
  int p = m;
  int cdlen = 0;
  char *cd = (char *)malloc(n*sizeof(char));
  //将各个结点的权重用于记录访问结点的次数,首先初始化为0
  for (int i=1; i<=m; i++) 
  {
    HT[i].weight = 0;
  }
  //一开始 p 初始化为 m,也就是从树根开始。一直到p为0
  while (p) 
  {
    //如果当前结点一次没有访问,进入这个if语句
    if (HT[p].weight == 0) 
    {
      HT[p].weight = 1;  //重置访问次数为1
      //如果有左孩子,则访问左孩子,并且存储走过的标记为0
      if (HT[p].left != 0) 
      {
        p = HT[p].left;
      }
      else if(HT[p].right == 0) // 当前结点没有左孩子,也没有右孩子,说明为叶子结点,直接记录哈夫曼编码
      {
        (*HC)[p] = (char*)malloc((cdlen+1)*sizeof(char));
        cd[cdlen] = '\0';
        strcpy((*HC)[p], cd);
      }
    }
    else if(HT[p].weight == 1) // 如果weiget为1,说明访问过一次,即使是左孩子返回的
    {
      HT[p].weight = 2;  //设置访问次数为2
      //如果有右孩子,遍历右孩子,记录标记值 1
      if (HT[p].right != 0) 
      {
        p = HT[p].right;
        cd[cdlen++] = '1';
      }
    }
    else //如果访问次数为2,说明左孩子都遍历完了,返回父结点
    {
      HT[p].weight = 0;
      p = HT[p].parent;
      --cdlen;
    }
  }
}

完整代码(可运行)

#include<stdlib.h>
#include<stdio.h>
#include<string.h>

//哈夫曼树结点结构
typedef struct 
{
  int weight;//结点权重
  int parent, left, right;//父结点、左孩子、右孩子在数组中的位置下标
}HTNode, *HuffmanTree;
//动态二维数组,存储哈夫曼编码
typedef char **HuffmanCode;
//HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置
void Select(HuffmanTree HT, int end, int *s1, int *s2)
{
  int min1, min2;
  //遍历数组初始下标为 1
  int i = 1;
  //找到还没构建树的结点
  while(HT[i].parent != 0 && i <= end)
  {
    i++;
  }
  min1 = HT[i].weight;
  *s1 = i;
  i++;
  while(HT[i].parent != 0 && i <= end)
  {
    i++;
  }
  //对找到的两个结点比较大小,min2为大的,min1为小的
  if(HT[i].weight < min1)
  {
    min2 = min1;
    *s2 = *s1;
    min1 = HT[i].weight;
    *s1 = i;
  }
  else
  {
    min2 = HT[i].weight;
    *s2 = i;
  }
  //两个结点和后续的所有未构建成树的结点做比较
  for(int j=i+1; j <= end; j++)
  {
    //如果有父结点,直接跳过,进行下一个
    if(HT[j].parent != 0)
    {
      continue;
    }
    //如果比最小的还小,将min2=min1,min1赋值新的结点的下标
    if(HT[j].weight < min1)
    {
      min2 = min1;
      min1 = HT[j].weight;
      *s2 = *s1;
      *s1 = j;
    }
    else if(HT[j].weight >= min1 && HT[j].weight < min2)   // 如果介于两者之间,min2赋值为新的结点的位置下标
    {
      min2 = HT[j].weight;
      *s2 = j;
    }
  }
}

//HT为地址传递的存储哈夫曼树的数组,w为存储结点权重值的数组,n为结点个数
void CreateHuffmanTree(HuffmanTree *HT, int *w, int n)
{
  if(n<=1) 
    return;     // 如果只有一个编码就相当于0
  int m = 2*n-1;   // 哈夫曼树总节点数,n就是叶子结点
  *HT = (HuffmanTree)malloc((m+1) * sizeof(HTNode)); // 0号位置不用
  HuffmanTree p = *HT;
  // 初始化哈夫曼树中的所有结点
  for(int i = 1; i <= n; i++)
  {
    (p+i)->weight = *(w+i-1);
    (p+i)->parent = 0;
    (p+i)->left = 0;
    (p+i)->right = 0;
  }

  //从树组的下标 n+1 开始初始化哈夫曼树中除叶子结点外的结点
  for(int i = n+1; i <= m; i++)
  {
    (p+i)->weight = 0;
    (p+i)->parent = 0;
    (p+i)->left = 0;
    (p+i)->right = 0;
  }

  //构建哈夫曼树
  for(int i = n+1; i <= m; i++)
  {
    int s1, s2;
    Select(*HT, i-1, &s1, &s2);
    (*HT)[s1].parent = (*HT)[s2].parent = i;
    (*HT)[i].left = s1;
    (*HT)[i].right = s2;
    (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
  }
}

//HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数
void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC, int n)
{
  *HC = (HuffmanCode) malloc((n+1) * sizeof(char *));
  char *cd = (char *)malloc(n*sizeof(char)); //存放结点哈夫曼编码的字符串数组
  cd[n-1] = '\0';//字符串结束符
  for(int i=1; i<=n; i++)
  {
    //从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放
    int start = n-1;
    //当前结点在数组中的位置
    int c = i;
    //当前结点的父结点在数组中的位置
    int j = HT[i].parent;
    // 一直寻找到根结点
    while(j != 0)
    {
      // 如果该结点是父结点的左孩子则对应路径编码为0,否则为右孩子编码为1
      if(HT[j].left == c)
        cd[--start] = '0';
      else
        cd[--start] = '1';
      //以父结点为孩子结点,继续朝树根的方向遍历
      c = j;
      j = HT[j].parent;
    }
    //跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码
    (*HC)[i] = (char *)malloc((n-start)*sizeof(char));
    strcpy((*HC)[i], &cd[start]);
  }
  //使用malloc申请的cd动态数组需要手动释放
  free(cd);
}

//打印哈夫曼编码的函数
void PrintHuffmanCode(HuffmanCode htable, int *w, int n)
{
  printf("Huffman code : \n");
  for(int i = 1; i <= n; i++)
    printf("%d code = %s\n",w[i-1], htable[i]);
}

int main(void)
{
  int w[5] = {2, 8, 7, 6, 5};
  int n = 5;
  HuffmanTree htree;
  HuffmanCode htable;
  CreateHuffmanTree(&htree, w, n);
  HuffmanCoding(htree, &htable, n);
  PrintHuffmanCode(htable, w, n);

  return 0;
}

/*运行结果
Huffman code :
code = 100
code = 11
code = 01
code = 00
code = 101*/

总结

在这里插入图片描述
图 4 程序运行效果图

本节的程序中对权重值分别为 2,8,7,6,5 的结点构建的哈夫曼树如图 4(A)所示。图 4(B)是另一个哈夫曼树,两棵树的带权路径长度相同。

程序运行效果图之所以是(A)而不是(B),原因是在构建哈夫曼树时,结点 2 和结点 5 构建的新的结点 7 存储在动态树组的最后面,所以,在程序继续选择两个权值最小的结点时,直接选择了的叶子结点 6 和 7 。

猜你喜欢

转载自blog.csdn.net/xia296/article/details/86524736