数据结构(哈夫曼树)

1. 哈夫曼树的基本概念

哈夫曼树又称最优树,是一类带权路径长度最短的树。

路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
路径长度:路径上的分支数目称作路径长度。
树的路径长度:从树根到每一结点的路径之和。
权:赋予某个实体的一个量,是对实体的某个或某些属性的数值描述。在数据结构中,实体有结点(元素)和边(关系)两大类,所以对应有结点权和边权。
结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长只和。
哈夫曼树:假设有m个权值{w1,w2,w3,…,wn},可以构造一棵含有n个叶子结点的二叉树,每个叶子结点的权为wi,则其中带权路径长度WPL最小的二叉树称做最优二叉树或哈夫曼树。

《数据结构(哈夫曼树)》

2.哈夫曼树的构造算法

(1) 哈夫曼树的构造过程

《数据结构(哈夫曼树)》
《数据结构(哈夫曼树)》

(2) 哈夫曼树的实现

由于哈夫曼树中没有度为1的结点,则一棵有n个叶子结点的哈夫曼树共有2n -1个结点,可以存储在一个大小为2n-1的一维数组中。
weight parent lchild rchild

//哈夫曼树的存储表示
typedef struct{
    int weight; //结点的权重
    int parent,lchild,rchild;//结点的双亲、左孩子、右孩子的下标
}HTNode,*HuffmanTree

哈夫曼树的各结点存储由HuffmanTree定义的动态分配的数组中,为了实现方便数组的0号单元不使用,从1号单元开始使用,所以数组的大小为2n。将叶子结点集中存储在前面部分1~n个位置,而后n-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。
// 选择权值最小的两颗树 
void SelectMin(HuffmanTree hT, int n, int &s1, int &s2)
{
    s1 = s2 = 0;

    int i;
    for(i = 1; i < n; ++ i){
        if(0 == hT[i].parent){
            if(0 == s1){
                s1 = i;
            }
            else{
                s2 = i;
                break;
            }
        }
    }
    if(hT[s1].weight > hT[s2].weight){
        int t = s1;
        s1 = s2;
        s2 = t;
    }

    for(i += 1; i < n; ++ i){
        if(0 == hT[i].parent){
            if(hT[i].weight < hT[s1].weight){
                s2 = s1;
                s1 = i;
            }else if(hT[i].weight < hT[s2].weight){
                s2 = i;
            }
        }
    }
}
// 构造有n个权值(叶子节点)的哈夫曼树 
void CreateHufmanTree(HuffmanTree &hT)
{
    int n, m;
    cin >> n;
    m = 2*n - 1;

    hT = new HTNode[m + 1];    // 0号节点不使用 
    for(int i = 1; i <= m; ++ i){
        hT[i].parent = hT[i].lChild = hT[i].rChild = 0;
    }
    for(int i = 1; i <= n; ++ i){
        cin >> hT[i].weight;    // 输入前n个单元中叶子结点的权值 
    }
    hT[0].weight = m;    // 用0号节点保存节点数量 

    /****** 初始化完毕, 创建哈夫曼树 ******/
    for(int i = n + 1; i <= m; ++ i){
          //通过n-1次的选择、删除、合并来创建二叉树
        int s1, s2;
        SelectMin(hT, i, s1, s2);

        hT[s1].parent = hT[s2].parent = i;
        hT[i].lChild = s1; hT[i].rChild = s2;    // 作为新节点的孩子 
        hT[i].weight = hT[s1].weight + hT[s2].weight;    // 新节点为左右孩子节点权值之和 
    }
}

C++ 完整代码

#include <iostream>
#include <iomanip>
using namespace std;

//哈夫曼树的存储表示
typedef struct
{
    int weight;    // 权值
    int parent, lChild, rChild;    // 双亲及左右孩子的下标 
}HTNode, *HuffmanTree;


// 选择权值最小的两颗树 
void SelectMin(HuffmanTree hT, int n, int &s1, int &s2)
{
    s1 = s2 = 0;

    int i;
    for(i = 1; i < n; ++ i){
        if(0 == hT[i].parent){
            if(0 == s1){
                s1 = i;
            }
            else{
                s2 = i;
                break;
            }
        }
    }
    if(hT[s1].weight > hT[s2].weight){
        int t = s1;
        s1 = s2;
        s2 = t;
    }

    for(i += 1; i < n; ++ i){
        if(0 == hT[i].parent){
            if(hT[i].weight < hT[s1].weight){
                s2 = s1;
                s1 = i;
            }else if(hT[i].weight < hT[s2].weight){
                s2 = i;
            }
        }
    }
}

// 构造有n个权值(叶子节点)的哈夫曼树 
void CreateHufmanTree(HuffmanTree &hT)
{
    int n, m;
    cin >> n;
    m = 2*n - 1;

    hT = new HTNode[m + 1];    // 0号节点不使用 
    for(int i = 1; i <= m; ++ i){
        hT[i].parent = hT[i].lChild = hT[i].rChild = 0;
    }
    for(int i = 1; i <= n; ++ i){
        cin >> hT[i].weight;    // 输入权值 
    }
    hT[0].weight = m;    // 用0号节点保存节点数量 

    /****** 初始化完毕, 创建哈夫曼树 ******/
    for(int i = n + 1; i <= m; ++ i){
        int s1, s2;
        SelectMin(hT, i, s1, s2);

        hT[s1].parent = hT[s2].parent = i;
        hT[i].lChild = s1; hT[i].rChild = s2;    // 作为新节点的孩子 
        hT[i].weight = hT[s1].weight + hT[s2].weight;    // 新节点为左右孩子节点权值之和 
    }
}

int HuffmanTreeWPL_(HuffmanTree hT, int i, int deepth)
{
    if(hT[i].lChild == 0 && hT[i].rChild == 0){
        return hT[i].weight * deepth;
    }
    else{
        return HuffmanTreeWPL_(hT, hT[i].lChild, deepth + 1) + HuffmanTreeWPL_(hT, hT[i].rChild, deepth + 1);
    }
}

// 计算WPL(带权路径长度) 
int HuffmanTreeWPL(HuffmanTree hT)
{
    return HuffmanTreeWPL_(hT, hT[0].weight, 0);
}

// 输出哈夫曼树各节点的状态 
void Print(HuffmanTree hT)
{
    cout << "index weight parent lChild rChild" << endl;
    cout << left;    // 左对齐输出 
    for(int i = 1, m = hT[0].weight; i <= m; ++ i){
        cout << setw(5) << i << " ";
        cout << setw(6) << hT[i].weight << " ";
        cout << setw(6) << hT[i].parent << " ";
        cout << setw(6) << hT[i].lChild << " ";
        cout << setw(6) << hT[i].rChild << endl;
    }
}

// 销毁哈夫曼树 
void DestoryHuffmanTree(HuffmanTree &hT)
{
    delete[] hT;
    hT = NULL;
}

int main()
{
    HuffmanTree hT;
    CreateHufmanTree(hT);
    Print(hT); 
    cout << "WPL = " << HuffmanTreeWPL(hT) << endl;
    DestoryHuffmanTree(hT);
    return 0;
}

《数据结构(哈夫曼树)》

    原文作者:yinxmm
    原文地址: https://www.jianshu.com/p/b9352dd3f406
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞