zoukankan      html  css  js  c++  java
  • 【数据结构】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
    请按任意键继续. . .

    特别说明

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

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

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

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

  • 相关阅读:
    基于 HTML5 Canvas 的交互式地铁线路图
    基于HTML5的WebGL实现json和echarts图表展现在同一个界面
    基于HTML5和WebGL的3D网络拓扑结构图
    JavaScript基础:创建对象
    使用ksoap2报java.io.EOFException异常问题解决方法
    Silverlight之我见
    今年的IT大趋势是虚拟现实
    Apache服务器部署ASP.NET网站
    Bootstrap优秀网站:乐窝网
    [转载]C#读取Excel几种方法的体会
  • 原文地址:https://www.cnblogs.com/robotpaul/p/10009312.html
Copyright © 2011-2022 走看看