【考研】设计求二叉树 T 的 WPL 算法(2014-408真题)

前言

本文内容源于对《数据结构(C语言版)》(严蔚敏著)和王道讲解的学习总结的笔记,以便考研复习。

主要针对 WPL 的算法进行解析,并扩展 WPL 的知识点,即求 WPL 最小的二叉树为哈夫曼树(即最优二叉树)的算法(构造哈夫曼树)。

可搭配以下链接一起学习:

【考研】常考的二叉树相关算法总结(详细全面)_住在阳光的心里的博客-CSDN博客

【考研】数据结构——线索二叉树_住在阳光的心里的博客-CSDN博客

【2023考研】数据结构常考应用典型例题(含真题)_住在阳光的心里的博客-CSDN博客 

 

一、题目

二叉树的带权路径长度(WPL)是二叉树中所有叶结点的带权路径长度之和。给定一棵二叉树,采用二叉链表存储,结点结构为

left weight right

其中叶结点的 weight 域保存该结点的非负权值。设 root 为指向 T 的根结点的指针,请设计求 T 的 WPL 的算法,要求:

(1)给出算法的基本设计思想。

(2)使用 C 或 C++ 语言,给出二叉树结点的数据类型定义。

(3)根据设计思想,采用 C 或 C++ 语言描述算法,关键之处给出注释。

二、解答

本题考查二叉树的带权路径长度 WPL 。

WPL:每个叶结点的深度与权值之积的总和,可使用先序遍历或层次遍历解决问题。

1、算法的基本设计思想

(1)基于先序遍历

用一个 static 变量记录 wpl ,把每个结点的深度作为递归函数的一个参数传递,算法步骤如下:

  • 若该结点是叶结点,则变量 wpl 加上该结点的深度与权值之积。
  • 若该结点是非叶结点,则左子树不为空时,对左子树调用递归算法,右子树不为空,对右子调用递归算法,深度参数均为本结点的深度参数加 1。
  • 最后返回计算出的 wpl 即可。

(2)基于层次遍历

使用队列进行层次遍历,并记录当前的层数:

  • 当遍历到叶结点时,累计 wpl。
  • 当遍历到非叶结点时,把该结点的子树加入队列。
  • 当某结点为该层的最后一个结点时,层数自增 1。
  • 队列空时遍历结束,返回 wpl。

2、二叉树结点的数据类型定义

typedef struct BiTNode{
    int weight;   //权值
    struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

3、算法代码

(1)基于先序遍历

int WPL(BiTree root){
    return wpl_PreOrder(root, 0);
}

int wpl_PreOrder(BiTree root, int deep){
    static int wpl = 0;   

    if(root->lchild == NULL && root->rchild == NULL)  //若为叶结点,则累积 wpl 
        wpl += deep*root->weight;

    if(root->lchild != NULL)   //若左子树不空,则对左子树递归遍历
        wpl_PreOrder(root->lchild, deep+1);

    if(root->rchild != NULL)   //若右子树不空,则对右子树递归遍历
        wpl_PreOrder(root->rchild, deep+1);

    return wpl;
}

(2)基于层次遍历

#define MaxSize 100   //设置队列的最大容量

int wpl_LevelOrder(BiTree root){
    BiTree q[MaxSize];    //q 为队列,最多容纳 MaxSize - 1 个元素
    int front = 0, rear = 0;   //头指针front、尾指针rear分别指向队头元素和队尾的后一个元素

    int wpl = 0, deep = 0;   //初始化 wpl 和深度
    //lastNode记录当前层的最后一个结点, newlastNode记录下一层的最后一个结点
    BiTree lastNode, newlastNode;   
    lastNode = root;  // lastNode 初始化为根结点
    newlastNode = NULL;   // newlastNode 初始化为空

    q[rear++] = root;  //根结点入队

    while(front != rear){  //层次遍历,若队列不空则循环
        BiTree t = q[front++];   //拿出队列中的头一个元素

        if(t->lchild == NULL && t->rchild == NULL)   //若为叶结点,统计wpl
            wpl += deep * t->weight;

        if(t->lchild != NULL){   //若为非叶结点,则把左结点入队
            q[rear++] = t->lchild;
            newlastNode = t->lchild;   //设下一层的最后一个结点为该结点的左结点
        }

        if(t->rchild != NULL){   //若为非叶结点,则把右结点入队
            q[rear++] = t->rchild;
            newlastNode = t->rchild;
        }

        if(t == lastNode){   //若该结点为本层最后一个结点,则更新lastNode
            lastNode = newlastNode;
            deep += 1;   //层数加一
        }
    }
    return wpl;
}

三、构造哈夫曼树

1、算法步骤

(1)初始化。

首先动态申请 2n 个单元

然后循环 2n - 1 次,从 1 号单元开始,依次将 1 至 2n - 1 所有单元中的双亲、左孩子、右孩子下标都初始化为 0;

最后再循环 n 次,输入前 n 个单元中叶子结点的权值。

(2)创建树。

循环 n - 1 次,通过 n - 1 次的选择、删除与合并来创建哈夫曼树。

选择是从当前森林中选择双亲为 0 且权值最小的两个树根结点 s1 和 s2;

删除是指将结点 s1 和 s2 的双亲改为非 0;

合并就是将 s1 和 s2 的权值和作为一个新结点的权值依次存入到数组的第 n + 1 之后的单元中,同时记录这个新结点左孩子的下标为 s1,右孩子的下标为 s2。

2、算法描述

typedef struct{
    int weight;   
    int parent, lchild, rchild;
}HTNode, *HuffmanTree;

//构造哈夫曼树 HT
void CreateHuffmanTree(HuffmanTree &HT, int n){
    if(n <= 1) return;

    int m = 2*n - 1;
    HT = new HTNode[m+1];   //0号单元未用,所以需动态分配 m+1 个单元,HT[m]表示根结点

    for(int i = 1; i <= m; ++i){  将 1 至 m 号单元中的双亲、左右孩子的下标都初始化为 0
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
    }

    for(int j = 1; j <= n; ++j){
        cin >> HT[i].weight;  //输入前 n 个单元中叶子结点的权值
    }

    //通过 n - 1 次的选择、删除与合并来创建哈夫曼树
    for(i = n + 1; i <= m; ++i){
        //在 HT[k] 中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
        Select(HT, i - 1, s1, s2);   

        HT[s1].parent = i;   //得到新结点i, 从森林中删除s1和s2,将s1和s2的双亲域由0改为i
        HT[s2].parent = i;

        HT[i].lchild = s1;   //s1、s2分别作为 i 的左右孩子
        HT[i].rchild = s2;

        HT[i].weight = HT[s1].weight + HT[s2].weight;   // i 的权值为左右孩子权值之和
    }
}

你可能感兴趣的:(数据结构,考研,C++,考研,数据结构,算法,学习,c++)