目录
实验目的及内容
农夫将木头锯成N块,要求总花费最少。请编写程序,输出锯木头的具体步骤以及总花费值。(要求构建哈夫曼树)
第一行输入正整数N(<=104),表示要将木头锯成N块。 第二行给出N个正整数(<=50),表示每段木块的长度
解题思路
根据题目的描述,可以发现这样的规律:要想省钱,越小的长度,越应该放到后面锯。
进一步抽象一下的话,就得到了解决方案:根据所有的目标木块长度来构建一棵哈夫曼树,把每段木块长度存在叶子中,根据法则生成根结点。最后,在三叉静态链表中顺序访问每一个根结点,即可输出锯木头的具体步骤。总花费即为所有根结点权值之和。
哈夫曼树的构造:
由于哈夫曼树中没有度为1 的结点,则一棵有n个叶子结点的哈夫曼树共有 2n-1个结点,可以存储在一个大小为2n-1的数组(静态三叉链表中。
为了实现方便,数组的0号单元不使用,从1号单元开始使用,所以数组的大小为2n,。将叶子结点集中存储在前面部分 1~n个位置。构造的时候,按顺序从数组的(n+1)号位置开始生成相应的根结点
实验代码及注释
本段代码使用常规方式构建哈夫曼树,其时间复杂度为O(n^2): Crt函数的for循环里调用了select函数,而select函数时间复杂度为O(n)
#include <stdio.h>
#include <iostream>
using namespace std;
#define N 104 /* 叶子结点的最大值*/
#define M 2 * N - 1 /* 所有结点的最大值*/
/*用静态三叉链表定义哈夫曼树的节点*/
typedef struct
{
int weight; /* 结点的权*/
int parent; /* 双亲的下*/
int LChild; /* 左孩子结点的下标*/
int RChild; /* 右孩子结点的下标*/
} HTNode, HuffmanTree[M + 1]; /* HuffmanTree 是一个结构体数组类型,0 号单元不用*/
// 选择两个双亲域为0且权值最小的结点,并将其在HT中的序号s1,s2通过引用的方式传递给实参
void select(HuffmanTree ht, int n, int &x1, int &x2)
{
int min; // 暂存序号
// 先找第一个序号s1
for (int i = 1; i < n; ++i) // 首先要找到第一个parent==0的节点,下标暂存min
{
if (ht[i].parent == 0)
{
min = i;
break;
}
}
for (int i = min + 1; i < n; ++i)
{
if (ht[i].parent == 0)
{
if (ht[i].weight < ht[min].weight)
{
min = i;
}
}
}
x1 = min;
// 接着找第二个s2
for (int i = 1; i <= n; ++i) // 首先要找到第一个parent==0的节点,下标暂存min
{
if (ht[i].parent == 0&&i!=x1 )
{
min = i;
break;
}
}
for (int i = 1; i <= n; ++i)
{
if (ht[i].parent == 0&&i!=x1)
{
if (ht[i].weight < ht[min].weight)
{
min = i;
}
}
}
x2 = min;
}
void CrtHuffmanTree(HuffmanTree ht, int w[], int n)
{
/*构造哈夫曼树,ht w[ ]存放着 n 个权值*/
for (int i = 1; i <= n; i++)
ht[i] = {w[i], 0, 0, 0}; /* 1 ~ n 号单元存放叶子结点,初始*/
int m = 2 * n ;
for (int i = n + 1; i <= m; i++)
ht[i] = {0, 0, 0, 0}; /* n+1 ~ m 号单元存放非叶结点,初始*/
/*—————————初始化完毕!下面开始构造造根结点————————*/
for (int i = n + 1; i <= m; i++)
{
int s1, s2;
select(ht, i - 1, s1, s2); /* 在ht[1] ~ ht[i-1] 的范围内选择两个 parent 为 0、且weight 最小的结点,其序号分别赋值给 s1、s2 */
ht[i].weight = ht[s1].weight + ht[s2].weight;
ht[s1].parent = i;
ht[s2].parent = i;
ht[i].LChild = s1;
ht[i].RChild = s2;
} /*哈夫曼树建立完毕*/
}
// 输出,从每个根结点开始输出,其左右子树便是这次锯出的结果
void Print(HuffmanTree ht, int n, int m)
{
int ALL =0;
cout<<"锯木头的步骤如下:"<<endl;
for (int i = m, j=1; i >= n + 1; j++,i--)
{
int l = ht[i].LChild;
int r = ht[i].RChild;
ALL+=ht[i].weight;
cout<<"第"<<j<<"次:"<<endl;
cout << ht[i].weight << "->" << ht[l].weight << "," << ht[r].weight << endl;
cout<<endl;
}
cout<<"最小总花费为:"<<ALL<<endl;
}
int main()
{
HuffmanTree ht;
int n;
cout << "请输入,要把木头锯成多少块:" << endl;
cin>>n;
int m = 2 * n - 1;
int weight[n];
cout<<"请输入每段木块的长度:"<<endl;
for (int i = 1; i <= n; ++i)
{
cin >> weight[i];
}
CrtHuffmanTree(ht, weight, n);
Print(ht, n, m);
return 0;
}
****************************
输入输出说明及结果截图
输入:
第一行输入正整数N(<=104),表示要将木头锯成N块。
第二行给出N个正整数(<=50),表示每 段木块的长度
输出:
锯木头的具体步骤以及总花费值
心得体会
通过本次实验,我加深了对哈夫曼树的理解,巩固了课堂学习知识,查漏补缺!