数据结构与算法——哈夫曼树

一、哈夫曼树的定义及构造思想

哈夫曼树定义:满足WPL最小的二叉树,即最优树
WPL(带权路径长度):设二叉树有n个叶结点,每个叶子结点带有权值wk,从根结点到每个叶结点长度为lk,则每个叶结点的WPL = ∑wk*lk

哈夫曼树特点:
1.没有度为1的结点,要么度为2要么度为0,因为每个结点都是由子结点两两合并而来。
2.哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树
3.n个叶结点的哈夫曼树共有2n-1个结点
简单证明:
n2——有2个子结点的结点
n1——有1个子结点的结点
n0——叶结点
n2 = n0 - 1
由于哈夫曼树没有n1,所以总结点数为n2+n0 = n - 1 + n = 2n - 1
4.对于同一组权值,存在不同的哈夫曼树

构造思想:每次把权值最小的两棵二叉树合并,合并后树的权值为合并前两棵树权值的和,所以这样子哈夫曼树的结构有点像金字塔,最顶端权值最大。那权值最小如何获得呢?我们可以想到用最小堆可以每次删除得到当前权值的最小值。

二、哈夫曼树用最小堆实现的代码

#include<iostream>

using namespace std;

#define MINDATA -100;//哨兵

//声明


typedef struct TreeNode* HuffmanTree;//哈夫曼树
struct TreeNode
{
    int Weight;//权重
    HuffmanTree Left,Right;//指向左右子树的指针
};


typedef struct HeapStruct* MinHeap;//最小堆
struct HeapStruct
{
    HuffmanTree *arr;//存储哈夫曼的数组
    int size;//当前元素个数
    int capacity;
};

MinHeap Create(int maxsize);//初始化最小堆
int IsFull(MinHeap H);//判断堆是否满了
void Insert(MinHeap H,HuffmanTree item);//向堆内插入元素,生成最小堆
HuffmanTree DeleteMin(MinHeap H);//删除最小堆的元素
HuffmanTree Initialize();//初始化哈夫曼树
HuffmanTree Huffman(MinHeap H);//生成哈夫曼树
void PreOrderTraversal(HuffmanTree T);//后序遍历哈夫曼树
MinHeap BuildMinHeap(MinHeap H);//调整成最小堆

MinHeap BuildMinHeap(MinHeap H)
{
    int i,parent,child;
    HuffmanTree temp;
    for(i=H->size/2;i>0;i--)
    {  //从最后一个父结点开始,直到根结点
        temp = H->arr[i];
        for(parent=i; parent*2<=H->size; parent=child)
        {		    /*向下过滤*/
            child = parent*2;
            if((child != H->size) && (H->arr[child]->Weight > H->arr[child+1]->Weight))
            {/*有右儿子,并且左儿子权值大于右儿子*/
                child++; //child指向左右儿子中较小者
            }
            if(temp->Weight > H->arr[child]->Weight)
            {
                    H->arr[parent] = H->arr[child];  //向上过滤结点-temp存放位置下移到child位置
            }else{
                    break;  //找到了合适的位置
            }
        }/*结束内部for循环对以H->data[i]为根的子树的调整*/
            H->arr[parent] = temp;  //temp(原H->data[i])存放到此处
    }
    return H;
}


MinHeap Create(int maxsize)
{
    MinHeap H = new struct HeapStruct;
    H->arr = new HuffmanTree [maxsize+1];//结构体内的数组存储的是哈夫曼树,加1是因为数组0下标,
    H->size = 0;
    H->capacity = maxsize;
    HuffmanTree T = Initialize();
    T->Weight = MINDATA;
    H->arr[0] = T ;//哨兵元素
    return H;
}

int IsFull(MinHeap H)
{
    if(H->size==H->capacity) return 1;
    else return 0;
}

void Insert(MinHeap H,HuffmanTree item)
{
    int i;
    if(IsNull(H))
    {
        cout << "最小堆已满"<<endl;
        return;
    }
    i = ++H->size;
    for(; H->arr[i/2]->Weight>item->Weight;i/=2)//其实如果最顶层元素仍比待插入元素小,那么还会继续往上顶,出现下标下益,解决方法加一个与条件:i>1,或者加一个哨兵
    {
        H->arr[i] = H->arr[i/2];
    }
    H->arr[i] = item;
}





HuffmanTree DeleteMin(MinHeap H)
{/*从最小堆H中取出权值为最小的元素,并删除一个结点*/
    int parent,child;
    HuffmanTree MinItem,temp = NULL;
    //if( IsFull(H) )
    //{
        //cout <<"最小堆为空\n";
        //return NULL;
   // }
    MinItem = H->arr[1];  //取出根结点-最小的元素-记录下来	/*用最小堆中的最后一个元素从根结点开始向上过滤下层结点*/
    temp = H->arr[H->size--];  //最小堆中最后一个元素,暂时将其视为放在了根结点
    for(parent=1; parent*2<=H->size; parent=child)
    {
        child = parent*2;
        if((child != H->size) && (H->arr[child]->Weight > H->arr[child+1]->Weight))
        {/*有右儿子,并且左儿子权值大于右儿子*/
            child++; //child指向左右儿子中较小者
        }
        if(temp->Weight > H->arr[child]->Weight)
        {
                H->arr[parent] = H->arr[child];  //向上过滤结点-temp存放位置下移到child位置
        }else{
                break;  //找到了合适的位置
        }
    }
    H->arr[parent] = temp;  //temp存放到此处

    return MinItem;
}




HuffmanTree Initialize()//初始化哈夫曼树
{
    HuffmanTree T = new struct TreeNode;
    T->Weight = 0;
    T->Left = T->Right = NULL;
    return T;
}

HuffmanTree Huffman(MinHeap H)//构造哈夫曼树的堆,直到原数组只剩除哨兵外下一个元素,也就是将原来的元素个数合并size-1次
{//这里应该还有一个判断堆是否空的函数
    int num;
    HuffmanTree T;
    //BuildMinHeap(H);//这个函数在这里可以注释掉,因为这里输入的值就是权值,对于原堆并非按路径权值的最小堆,需要重新按权值生成最小堆,当然这种最小堆数组存储不止权重一个元素
    num = H->size;
    for(int i=1;i<num;i++)//合并size-1次,这里必须要有一个num作为中间变量,否则结构不正确,
    {
        T = new struct TreeNode;
        HuffmanTree Tmin1 = DeleteMin(H);
        HuffmanTree Tmin2 = DeleteMin(H);
        T->Weight = Tmin1->Weight+Tmin2->Weight;
        T->Left = Tmin1;
        T->Right = Tmin2;
        Insert(H,T);
    }
    T = DeleteMin(H);//此时最小堆只剩下除哨兵外的一个元素
    return T;
}

void PreOrderTraversal(HuffmanTree T)
{
    if(T)
    {
        cout << T->Weight<<endl;
        PreOrderTraversal(T->Left);
        PreOrderTraversal(T->Right);

    }
}

int main()
{
    int item;
    MinHeap H;
    HuffmanTree T;
    cout << "请输入您需要创建的最小堆的元素个数" <<endl;
    cin >> item;
    H = Create(item);
    cout << "请输入各叶子对应的最小权值"<<endl;
    for(int i=1;i<=7;i++)
    {
        T = Initialize();
        cin >> T->Weight;
        Insert(H,T);
    }
    //Huffman(H);
    cout<< "先序遍历哈夫曼树"<<endl;
    PreOrderTraversal(Huffman(H));
    return 0;
}

结果:
在这里插入图片描述

三、哈夫曼编码:

跟据哈夫曼树形成的非前缀编码,一个哈夫曼树的叶结点对应一个字符,附一张图:
在这里插入图片描述
代码实现:在之前输入的7个叶结点上,对应7个字符

typedef struct TreeNode* HuffmanTree;
struct TreeNode
{
    int Weight;
    char ch;//增加了一个代表字符的变量,叶结点会被赋值相应的代表字符,非叶结点默认为NULL
    HuffmanTree Left,Right;
};

void PreOrderTraversal(HuffmanTree T)
{
    if(T)
    {
        cout << T->Weight<<endl;
        PreOrderTraversal(T->Left);
        PreOrderTraversal(T->Right);

    }
}


void HuffmanCode(HuffmanTree BST,int depth)  //哈夫曼编码
{
    static int code[10];  //编码空间
    if( BST )
    {
        if( (BST->Left == NULL) && (BST->Right == NULL))
        {  //找到了叶结点
            printf("字符%c对应权值为%d的哈夫曼编码为:",BST->ch,BST->Weight);
            for(int i=0; i<depth; i++)
            {
                printf("%d",code[i]);
            }
            printf("\n");
        }else{
            code[depth] = 0;  //往左子树方向编码为0,经典的树结构递归,从左至右
            HuffmanCode(BST->Left,depth+1);
            code[depth] = 1;  //往右子树方向编码为1
            HuffmanCode(BST->Right,depth+1);
             }
    }

}

void HuffmanDecode(char ch[],HuffmanTree BST)  //ch[] 要解码的序列
{
    int cnt;
    int num[10];
    HuffmanTree temp;
    for(int i=0; i<strlen(ch); i++)
    {
        if(ch[i] == '0')
        {
            num[i] = 0;
        }else{
            num[i] = 1;
        }
    }
    if( BST )
    {
        cnt = 0;  //计数已经解码0101串的长度
        while(cnt < strlen(ch))
        {
            temp = BST;
            while((temp->Left != NULL ) && (temp->Right != NULL))
            {
                if(num[cnt] == 0)
                {
                        temp = temp->Left;
                }else{
                        temp = temp->Right;
                }
                cnt++;
            }
            printf("%c",temp->ch);  //输出解码后对应结点的字符
        }
    }
}

结果:
在这里插入图片描述

code[depth] = 0;  
HuffmanCode(BST->Left,depth+1);
code[depth] = 1;  
HuffmanCode(BST->Right,depth+1);

链接资源:最小堆实现哈夫曼树的构造及哈夫曼编码、解码

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值