一、【实验目的】
(1)进一步理解贪心法的设计思想
(2)掌握哈夫曼算法的具体应用
(3)比较不同的文件归并策略,探讨最优算法。
二、【实验内容】
设S={f1,…,fn}是一组不同的长度的有序文件构成的集合,其中fi表示第i个文件的记录个数,现使用二分归并法将这些文件归并成一个有序文件,归并过程可以看成是一棵二叉树.参考教材P102例4.7,请采用两种方法进行二分归并,其中一种为哈夫曼算法。
提示:其中n个文件可用n个数组模拟,文件的内容为数组中已排好序的整数;可以用教材例4.7中定义的S集合中6个文件的长度作为数组长度进行测试。注意,要编写两个数组进行顺序归并的程序。
三、实验源代码
四、实验结果
五、实验分析与总结
实验分析:
要求完成以下两种归并
按照哈夫曼算法的思路,先取最小的两个点进行归并,得到一个父结点。再放回去再选择两个小的。
⚠️错误写法
- 这里用的优先队列实现了插入一个元素后排序且可以有队列的特性。
- j用于叶子结点的创建,i用于父结点的创建。
- returnElement函数取最小的两个元素,再加起来放回去
#include <bits/stdc++.h>
using namespace std;
int n = 6;
vector<int> s = {21, 10, 32, 41, 18, 70};
vector<int> a = s;
vector<int> temp(10);
priority_queue<int, vector<int>, greater<int>> pq; //升序队列模板
struct treeNode{
int weight;
int parent;
int leftchild, rightchild;
}tree[20];
void returnElement(int& element1, int& element2)
{
if(!pq.empty())
{
element1 = pq.top();
pq.pop();
element2 = pq.top();
pq.pop();
int sum = element1 + element2;
pq.push(sum);
}
}
void CreateHuffmanTree(){ //二分归并法最优
//输入
for(auto e : s)
pq.push(e);
int j=1, element1, element2;
//初始化-1
for(int i=1; i<=2*n-1; i++)
{
tree[i].parent = -1, tree[i].leftchild = -1;
tree[i].rightchild = -1;
}
for(int i=n; i<=2*n-1; i++)
{
//左右孩子结点
returnElement(element1, element2);
tree[j].weight = element1;
j++;
tree[j].weight = element2;
j++;
//父节点
tree[i].weight = element1 + element2;
tree[i].leftchild = j-2;
tree[i].rightchild = j-1;
}
}
//由于中文乱码,所以输出英文
void printTree()
{
for (int i = 1; i <=2*n-1; i++)
{
cout << setw(7) << "i:" << i ;
cout << setw(7) << "node:" << tree[i].weight << "\t";
cout << setw(7) << "parent:" << tree[tree[i].parent].weight << "\t";
cout << setw(7) << "leftchild:" << tree[tree[i].leftchild].weight << "\t";
cout << setw(7) << "rightchild:" << tree[tree[i].rightchild].weight << endl;
}
}
int main()
{
CreateHuffmanTree();
printTree();
return 0;
}
运行结果:
- 叶子结点和父结点交叉存放
- -1的值未打印出来
- 有一些数据被覆盖掉了
对比书上的图,缺项多项
正确写法:
【【数据结构】构造哈夫曼树手写代码】https://www.bilibili.com/video/BV1EV4y1V7Vx/?share_source=copy_web&vd_source=7ffbd7feaeedb3d59fb21e59435a53d8 改进:
returnElement函数弃用,改成每次在tree中寻找最小元素、和次小元素的下标。
先把叶子结点存入,以免出现交叉存放和覆盖数据
先把叶子结点存入后,直接更新父结点的值就可以了,叶子结点的parent指向父结点的下标
#include <bits/stdc++.h>
using namespace std;
int n = 6;
vector<int> s = {21, 10, 32, 41, 18, 70};
vector<int> a = s;
vector<int> temp(10);
//priority_queue<int, vector<int>, greater<int>> pq; //升序队列模板
struct treeNode{
int weight;
int parent;
int leftchild, rightchild;
}tree[20];
void selectMinElementIndex(int n, int& index1, int& index2)
{
int min = 9e8; //打擂台,用于确定最小的那个
//寻找最小的权值下标
for(int i=1; i<= n; i++)
{
if(tree[i].parent == -1 && tree[i].weight < min)
{
min = tree[i].weight;
index1 = i;
}
}
min = 9e8;
//寻找次小的权值下标
for(int i=1; i<= n; i++)
{
if(tree[i].parent == -1 && tree[i].weight < min && i != index1)
{
min = tree[i].weight;
index2 = i;
}
}
}
void CreateHuffmanTree(){ //二分归并法最优
sort(s.begin(), s.end());
int index1, index2;
//初始化-1
for(int i=1; i<=2*n-1; i++)
{
tree[i].parent = -1, tree[i].leftchild = -1;
tree[i].rightchild = -1;
}
//放入叶子结点
for(int i=1; i<=n; i++)
{
tree[i].weight = s[i-1];
}
for(int i=n+1; i<=2*n-1; i++)
{
//传入了i-1,保证在新建立的父节点和叶子结点中寻找两个最小
selectMinElementIndex(i-1, index1, index2);
// 左右孩子结点指向父结点
tree[index1].parent = i;
tree[index2].parent = i;
//父结点更新值
tree[i].weight = tree[index1].weight + tree[index2].weight;
tree[i].leftchild = index1;
tree[i].rightchild = index2;
}
}
//由于中文乱码,所以输出英文
void printTree()
{
for (int i = 1; i <= 2*n-1; i++)
{
cout << setw(7) << "i:" << i << " ";
cout << setw(7) << "node:" << tree[i].weight << "\t";
cout << setw(7) << "parent:" << (tree[i].parent != -1 ? tree[tree[i].parent].weight : -1) << "\t";
cout << setw(7) << "leftchild:" << (tree[i].leftchild != -1 ? tree[tree[i].leftchild].weight : -1) << "\t";
cout << setw(7) << "rightchild:" << (tree[i].rightchild != -1 ? tree[tree[i].rightchild].weight : -1) << endl;
}
}
void CreateATree(){ //另一种二分归并法
int j=1;
//初始化-1
for(int i=1; i<=2*n-1; i++)
{
tree[i].parent = -1, tree[i].leftchild = -1;
tree[i].rightchild = -1;
}
//放入叶子结点
for(int i=1; i<=n; i++)
{
tree[i].weight = s[i-1];
}
for(int i=n+1; i<=2*n-1; i++)
{
// 左右孩子结点指向父结点,j直接选两个平推过去
tree[j].parent = i;
tree[j+1].parent = i;
//父结点更新值
tree[i].weight = tree[j].weight + tree[j+1].weight;
tree[i].leftchild = j;
tree[i].rightchild = j+1;
j += 2;
}
}
int main()
{
cout << "The another method:" << endl;
CreateATree();
printTree();
cout << "The best method:" << endl;
CreateHuffmanTree();
printTree();
return 0;
}