建立:
为了使得树的带权路径最短,需要将权值较大的结点放在较底层,较小的放在较上层。
实现这个过程需要借助队列结构。
用队列存储结点地址,每次选取权值最小的两个结点并出队(可以事先排好次序),申请一个新结点作为它们的父结点,并将父结点入队。重复这个过程直到队列只剩一个结点,即根结点。
//定义结点
typedef struct HuffmanTree
{
int weight=0;//权值
HuffmanTree *lchild=nullptr;//左子树
HuffmanTree *rchild=nullptr;//右子树
HuffmanTree *parent=nullptr;//父结点
}HuffmanTree;
void CreatByQueue(HuffmanTree *&T)
{
//先对输入的数据排序,再保存到队列中。
vector<int> v;
cout << "请输入各个叶子的权值\n(结束输入:Ctrl+Z)" << endl;
int w;
while(cin >> w)
{
v.push_back(w);
}
//排序数组
sort(v.begin(),v.end());
//将数据搬运至队列
queue<HuffmanTree*> q;
HuffmanTree *t=nullptr;//负责开辟新结点
int n=v.size();
for(int i=0;i<n;i++)
{
t=new HuffmanTree;
t->weight=v[i];
q.push(t);
}
//此时队列中存放了树的所有叶子,每次出队两个结点,用一个父结点将它们连接起来,再将父结点入队。
HuffmanTree *a=nullptr;
HuffmanTree *b=nullptr;
while(q.size()!=1)
{
a=q.front();
q.pop();
b=q.front();
q.pop();
t=new HuffmanTree;//申请父结点
t->weight=a->weight+b->weight;
t->lchild=a;
t->rchild=b;
a->parent=t;
b->parent=t;
q.push(t);
}
T=q.front();//剩一个结点,即根结点,保存了权值的最终结果
q.pop();
}
编码:
设置一个指针获取队头的叶子结点,然后一步一步回退到根结点,由0和1组成的编码保存在数组中,回退过程中通过判断叶子是双亲的左子树还是右子树来决定存储0或1,由于是从叶子结点回溯到根结点,编码顺序是反的,所以最后还需要将数组中的编码反转。
//递归寻找二叉树中的叶子并将其地址压入队列:
void search_leaf(HuffmanTree *&T,queue<HuffmanTree*> &q)
{
if(T==nullptr) return;
if(T->lchild==nullptr && T->rchild==nullptr) q.push(T);
search_leaf(T->lchild,q);
search_leaf(T->rchild,q);
}
//编码:
void HuffmanCoding(HuffmanTree *&T)
{
queue<HuffmanTree*> q;//存放叶子的队列
search_leaf(T,q);//获取树中的叶子结点
vector<vector<int>> HTCode;
HuffmanTree *t;//指向扫描到的结点
int n=q.size();
HTCode.resize(n);
for(int h=0;!q.empty();++h)
{
t=q.front();
q.pop();
while(t!=T)//回退到根节点则退出循环
{
if(t->parent->lchild==t)
{
HTCode[h].push_back(0);
}
else if(t->parent->rchild==t)
{
HTCode[h].push_back(1);
}
t=t->parent;//回退
}
//反转数组内的元素
int s=HTCode[h].size();
for(int i=0,j=s-1;i<s/2;++i,--j)
swap(HTCode[h][i],HTCode[h][j]);
//测试:查看编码结果
//for(int i=0;i<s;i++) cout << HTCode[h][i];
//cout << endl;
}
}