【数据结构】3-2哈夫曼树的实现(数组实现)以及哈夫曼编码

哈夫曼树的性质

  1. 哈夫曼树不唯一(即左孩子右孩子放的顺序可以是左大右小也可以是左小右大)
  2. 哈夫曼树的子树也是哈夫曼树
  3. 哈夫曼树中无度为1的结点
  4. 有n个叶子结点的哈夫曼树,其总结点数为2*n-1(非常重要!编程实现就要用这条性质)

总体思路

  1. 对两个最小者的选择为双亲下标为0的结点
  2. 对选出的两个最小者,修改双亲下标为新结点的下标
  3. 新结点的左右孩子修改为所选的两个新结点的下标
  4. 新结点的权值为两个结点的权值之和

结构体定义

struct element
{
    int weight;        // 权值域
    int lchild, rchild, parent;  // 该结点的左、右、双亲结点在数组中的下标
};

哈夫曼树类的定义

class HTree
{
private:
    element *node;
    int n, m;//n是元素个数
    stack<int> code;
    int setcode(int weight);//返回的是编码的长度
public:
    HTree(int _n, int _weight[]);
    void select(int &s1, int &s2);
    void creatHT();
    void print();
    int search(int weight);
    void printHTcode();
};

构造函数的实现

HTree::HTree(int _n,int _weight[])
{
    n = _n;
    m = 2 * n - 1;            //n个元素需要2*n-1空间
    node = new element[m];    //开辟m个大小的element空间
    for (int i = 0; i <n; i++)
    {
        node[i].weight = _weight[i];//为每一个元素赋初始权值
    }
    for (int i = 0; i < m; i++)
    {
        node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化左孩子右孩子和双亲指针,都为0
    }
    creatHT();//构造哈夫曼树
}

选择函数,比较结点中双亲为0的较小的两个(在本文中s1最小)

void HTree::select(int &s1, int &s2)
{
    //对两个最小者的选择为双亲下标为0的结点
    for (int i = 0; i < n; i++)
    {
        if (node[i].parent == 0)
        {
            s1=i;    //先找到一个临时比较元素进行最小值比较
            break;
        }
    }
    for (int i = 0; i <n; i++)        //该循环是找到一个最小的并赋值给s1
    {
        if ((node[s1].weight > node[i].weight) && (node[i].parent == 0))        //找双亲为0的
        {
            s1 = i;
        }
    }
    //同s1的比较方法,先找到一个双亲为0的临时值(排除s1),然后再进行逐一比较找到第二小的值赋值给s2即可
    for (int i = 0; i <n; i++)
    {
        if ((node[i].parent == 0)&&i!=s1)        //排除s1
        {
            s2 = i;    //先找到一个临时比较元素进行最小值比较
            break;
        }
    }
    for (int i = 0; i <n; i++)        //找到除s1以外双亲为0中最小的并赋值给s2
    {
        if ((node[s2].weight > node[i].weight) && (node[i].parent == 0) && (i != s1))
        {
            s2 = i;
        }
    }
    
}

创建哈夫曼树

  1. 循环2*n-1 - n-1次,即进行m-n-1次比较即可
  2. 对两个最小者的选择为双亲下标为0的结点(调用选择函数来实现)
  3. 对选出的两个最小者,修改双亲下标为新结点的下标
  4. 新结点的左右孩子修改为所选的两个新结点的下标
  5. 新结点的权值为两个结点的权值之和
void HTree::creatHT()
{
    for (int i = n; i < m; i++)//执行的是m-n次比较
    {
        int s1, s2;//左小右大
        select(s1, s2);//选择数组中最小且双亲为0的结点
        node[s1].parent = i;//找到后的s1,s2的双亲应该放在n-1后
        node[s2].parent = i;
        node[i].lchild = s1;    //左小
        node[i].rchild = s2;    //右大
        node[i].weight = node[s1].weight + node[s2].weight;
        n++;
    }
}

哈夫曼编码:

原理:

  1. 输入要编码的权值,调用search函数遍历查找到在数组中的地址并返回
  2. 从要编码的位置上溯遍历,并判断是左孩子还是右孩子,左孩子为0右孩子为1(也可以反过来)
  3. 编码是从上到下,而我们在遍历的时候是从下到上,因此可以使用栈或者链表的头插法进行存储操作(我在这里使用的是栈,有点不好,输出栈就被清空了,可能会影响时间效率)
  4. 判断如果该结点的parent为空(即为0)停止循环即可

setcode(私有函数)

int HTree::setcode(int weight)
{
    int location, parent, k;//location来定位权值所指向的位置,parent即Location的双亲,k是移动指针
    int cont = 0;
    location = search(weight);
    k = location;
    parent = node[location].parent;
    while (node[k].parent!= 0&&k!=-1)
    {
        if (node[parent].lchild == k)
        {
            code.push(0);
        }
        else
        {
            code.push(1);
        }
        k = node[k].parent;
        parent = node[k].parent;
        cont++;
    }
    if (k == -1)
    {
        cout << "未找到!错误!" << endl;
    }
    return cont;
}

search函数

int HTree::search(int weight)
{
    int result = -1;
    for (int i = 0; i < m; i++)
    {
        if (node[i].weight == weight)
        {
            result = i;
        }
    }
    return result;
}

完整代码:

/*
1.对两个最小者的选择为双亲下标为0的结点
2.对选出的两个最小者,修改双亲下标为新结点的下标
3.新结点的左右孩子修改为所选的两个新结点的下标
4.新结点的权值为两个结点的权值之和
*/
#include<iostream>
#include<iomanip>
#include<stack>
using namespace std;
struct element
{
    int weight;        // 权值域
    int lchild, rchild, parent;  // 该结点的左、右、双亲结点在数组中的下标
};
class HTree
{
private:
    element *node;
    int n, m;//n是元素个数
    stack<int> code;
    int setcode(int weight);//返回的是编码的长度哼哼
public:
    HTree(int _n, int _weight[]);
    void select(int &s1, int &s2);
    void creatHT();
    void print();
    int search(int weight);
    void printHTcode();
};
int HTree::search(int weight)
{
    int result = -1;
    for (int i = 0; i < m; i++)
    {
        if (node[i].weight == weight)
        {
            result = i;
        }
    }
    return result;
}
int HTree::setcode(int weight)
{
    int location, parent, k;//location来定位权值所指向的位置,parent即Location的双亲,k是移动指针
    int cont = 0;
    location = search(weight);
    k = location;
    parent = node[location].parent;
    while (node[k].parent!= 0&&k!=-1)
    {
        if (node[parent].lchild == k)
        {
            code.push(0);
        }
        else
        {
            code.push(1);
        }
        k = node[k].parent;
        parent = node[k].parent;
        cont++;
    }
    if (k == -1)
    {
        cout << "未找到!错误!" << endl;
    }
    return cont;
}
void HTree::printHTcode()
{
    cout << "请输入要查询的编码的权值并继续:" << endl;
    int weight, size;
    cin >> weight;
    size=setcode(weight);
    cout << "权值 " << weight << "编码长度: " << size << endl;
    cout << weight << " 编码结果为: ";
    for (int i = 0; i < size; i++)
    {
        cout << code.top() << " ";
        code.pop();
    }
    cout << endl;
}
HTree::HTree(int _n,int _weight[])
{
    n = _n;
    m = 2 * n - 1;            //n个元素需要2*n-1空间
    node = new element[m];    //开辟m个大小的element空间
    for (int i = 0; i <n; i++)
    {
        node[i].weight = _weight[i];//为每一个元素赋初始权值
    }
    for (int i = 0; i < m; i++)
    {
        node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化左孩子右孩子和双亲指针,都为0
    }
    creatHT();//构造哈夫曼树
}
void HTree::select(int &s1, int &s2)
{
    //对两个最小者的选择为双亲下标为0的结点
    for (int i = 0; i < n; i++)
    {
        if (node[i].parent == 0)
        {
            s1=i;    //先找到一个临时比较元素进行最小值比较
            break;
        }
    }
    for (int i = 0; i <n; i++)        //该循环是找到一个最小的并赋值给s1
    {
        if ((node[s1].weight > node[i].weight) && (node[i].parent == 0))        //找双亲为0的
        {
            s1 = i;
        }
    }
    //同s1的比较方法,先找到一个双亲为0的临时值(排除s1),然后再进行逐一比较找到第二小的值赋值给s2即可
    for (int i = 0; i <n; i++)
    {
        if ((node[i].parent == 0)&&i!=s1)        //排除s1
        {
            s2 = i;    //先找到一个临时比较元素进行最小值比较
            break;
        }
    }
    for (int i = 0; i <n; i++)        //找到除s1以外双亲为0中最小的并赋值给s2
    {
        if ((node[s2].weight > node[i].weight) && (node[i].parent == 0) && (i != s1))
        {
            s2 = i;
        }
    }
    
}
void HTree::creatHT()
{
    for (int i = n; i < m; i++)//执行的是m-n次比较
    {
        int s1, s2;//左小右大
        select(s1, s2);//选择数组中最小且双亲为0的结点
        node[s1].parent = i;//找到后的s1,s2的双亲应该放在n-1后
        node[s2].parent = i;
        node[i].lchild = s1;    //左小
        node[i].rchild = s2;    //右大
        node[i].weight = node[s1].weight + node[s2].weight;
        n++;
    }
}
void HTree::print()
{
    cout << "index weight parent lChild rChild" << endl;
    cout << left;    // 左对齐输出 
    for (int i = 0; i < m; ++i)
    {
        cout << setw(5) << i << " ";
        cout << setw(6) << node[i].weight << " ";
        cout << setw(6) << node[i].parent << " ";
        cout << setw(6) << node[i].lchild << " ";
        cout << setw(6) << node[i].rchild << endl;
    }
    cout << "哈夫曼树打印完毕!" << endl;
}
int main()
{
    cout << "请输入要构造哈夫曼树元素的个数: ";
    int n,*weight;
    cin >> n;
    weight = new int[n + 1];
    cout << "请输入这" << n << "个元素的权值:" << endl;
    for (int i = 0; i < n; i++)
    {
        cin >> weight[i];
    }
    HTree test(n, weight);
    test.print();
    test.printHTcode();
    system("pause");
    return 0;
}
点我查看完整代码

测试数据

4

5 2 4 7

4

结果

请输入要构造哈夫曼树元素的个数: 4
请输入这4个元素的权值:
5 2 4 7
index weight parent lChild rChild
0     5      5      0      0
1     2      4      0      0
2     4      4      0      0
3     7      6      0      0
4     6      5      1      2
5     11     6      0      4
6     18     0      3      5
哈夫曼树打印完毕!
请输入要查询的编码的权值并继续:
4
权值 4编码长度: 3
4 编码结果为: 1 1 1
请按任意键继续. . .

特别说明

这个代码绝对不是最优解:)

但是原理是对的,嘛毕竟自己能力有限~

只要原理懂了可以自己优化优化的~~

各位见笑了,还请多多包涵。

原文地址:https://www.cnblogs.com/robotpaul/p/10009312.html