一、实验目的
1. 掌握贪心法的基本原理和实现思路。
2. 理解用贪心法求解的问题应具有的性质。
3. 理解贪心法的一般求解过程。
4. 理解贪心法的应用
二、实验环境
1. 操作系统:Windows 10及以上。
2. 程序设计软件:Microsoft Visual C++、Microsoft Visual Studio或DEV C++等。
三、实验要求
1. 完整记录实验过程,包括算法设计思想描述、代码实现和程序测试。
2. 按照实验内容编写相应功能的程序,记录完成步骤、结果及在实验过程中所遇到的问题。
四、实验内容
1. 求解活动安排问题。假设有一个需要使用某一资源的n个活动所组成的集合S,S={1,…,n}。该资源任何时刻只能被一个活动所占用,活动i有一个开始时间bi和结束时间ei(bi<ei),其执行时间为ei-bi,假设最早活动执行时间为0。一旦某个活动开始执行,中间不能被打断,直到其执行完毕。若活动i和活动j有bi≥ej或bj≥ei,则称这两个活动兼容。设计算法求一种最优活动安排方案,使得所有安排的活动个数最多。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
2. 求解最优装载问题。有n个集装箱要装上一艘载重量为W的轮船,其中集装箱i(1≤i≤n)的重量为wi。不考虑集装箱的体积限制,现要选出尽可能多的集装箱装上轮船,使它们的重量之和不超过W。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
3. 哈夫曼编码问题。设要编码的字符集为{d1, d2, …, dn},它们出现的频率为{w1,w2, …,wn},应用哈夫曼树构造最优的不等长的由0、1构成的编码方案。描述算法设计思想,编写完整的实验程序,并采用相应数据进行测试。
五、实验步骤
1. 求解活动安排问题。
算法设计思想描述:
先利用重载排序活动结束时间,使其依次递增排序,选结束时间最早的活动来占用资源,假设一个变量来记录前一个兼容活动的结束时间,记录结束时间最早的活动结束时间,下一步选择开始时间大于等于结束时间最早的活动的结束时间,使其开始时间变成选择的活动的结束时间,按照这个方法依次选择,直至排序后活动结束时间的最后一个活动,得出最大兼容活动的子集,即所有安排活动个数最多。
代码实现:
#include<stdio.h>
#include<string.h>
//使用memset()函数
#include<algorithm>
//C++STL库中的一个头文件,提供了一些常用的算法函数,如排序、查找、合并等
//排序操作:sort:对指定范围内的元素进行排序。
using namespace std;
#define MAX 51//宏定义
//问题表示
struct Action//活动类型声明
{
int b,e;//b活动起始时间(begin)、e活动结束时间 (end)
bool operator<(const Action &s)const//重载<关系函数
{
return e<=s.e;//用于按活动结束时间递增排序
}
};
int n=11;//11个活动
Action A[]={{0},{1,4},{3,5},{0,6},{5,7},{3,8},{5,9},{6,10},{8,11},{8,12},{2,13},{12,15}};
//所有活动,下标为0的元素不用
bool flag[MAX];//标记选择的活动
//bool类型的函数返回值只有两种可能:true(真)或false(假)
//bool判断返回值为true还是false,若是true,才会执行if语句内的操作
int Count=0;//选取的兼容活动个数
void solve()//求取最大兼容活动子集
{
memset(flag,0,sizeof(flag));//初始化为false
sort(A+1,A+n+1);//A[1...n]按活动结束时间递增排序
int preend=0;//前一个兼容活动的结束时间
for(int i=1;i<=n;i++)//扫描所有活动
{
if(A[i].b>=preend)//找到一个兼容活动 //>=半闭区间
{
flag[i]=true;//选择A[i]活动
preend=A[i].e;//更新preend值
//前一个兼容活动的结束时间变成当前选择的活动结束时间
}
}
}
int main()
{
solve();//调用贪心法求解活动安排问题
printf("求解结果如下:\n");
printf("选取的活动为:");
for(int i=1;i<=n;i++)//扫描所有活动
if(flag[i])//选择活动
{
printf("[%d,%d) ",A[i].b,A[i].e);//输出选择的活动半闭区间
Count++;//累计选取的兼容活动个数
}
printf("\n一共有%d个活动\n",Count);
}
![]() |
程序测试:
int n=10;
Action A[]={{0},{2,6},{1,8},{5,9},{3,4},{6,10},{4,12},{7,5},{9,14},{11,12},{10,11}};
![]() |
2. 求解最优装载问题。
算法设计思想描述:
要想装尽可能多的集装箱,且不考虑体积,即尽量选重量轻的集装箱,就可以装得多,故先把集装箱按重量递增排序,依次从最轻的集装箱放入,记录总重量比较限重,如果选择的集装箱超出重量,就不选最后选择的集装箱,最后得到的就是最多装载集装箱的集合。
代码实现:
#include<stdio.h>
#include<string.h>
//使用memset()函数
#include<algorithm>
//C++STL库中的一个头文件,提供了一些常用的算法函数,如排序、查找、合并等
//排序操作:sort:对指定范围内的元素进行排序。
using namespace std;
#define MAXN 20//宏定义,最多集装箱个数
//问题表示
int w[]={0,5,2,6,4,3};//各集装箱重量,不用下标为0的元素
int n=5,W=10;//n集装箱个数,W船的重量(限重 )
int maxw,x[MAXN];//maxw存放最优解的总重量,x[MAXN]存放最优解向量
void solve()//求解最优装载问题
{
memset(x,0,sizeof(x));//初始化解向量
sort(w+1,w+n+1);//w[1...n]递增排序
maxw=0;//没放集装箱之前,最优解的总重量为0
int restw=W;//剩余重量,没放集装箱之前,剩余重量是船的重量,即限重
for(int i=1;i<=n&&w[i]<=restw;i++)
//选择的集装箱i小于等于集装箱个数,集装箱重量小于等于剩余重量(限重)
{
x[i]=1;//选择集装箱i
restw-=w[i];//减少剩余重量,即还能放入的集装箱重量
maxw+=w[i];//累计装载总重量
}
}
int main()
{
solve();//调用贪心法求解最优装载问题
printf("最优方案如下:\n");
for(int i=1;i<=n;i++)//扫描所有活动
if(x[i]==1)//选择集装箱
printf("%d:选取重量为%d的集装箱\n",i,w[i]);
printf("总重量=%d\n",maxw);
}
![]() |
程序测试:
int w[]={0,9,6,2,7,4,3,1};
int n=7,W=12;
![]() |
3. 哈夫曼编码问题。
算法设计思想描述:
哈夫曼树需要使带权路径长度最小,故权值越大的结点越靠近根结点,使其权值大的结点离根结点的路径长度更短,两者乘积越小;权值越小的结点越远离根结点,总的带权路径长度最小。我们选取权值最小和次小的构成二叉树,指定权值最小的在左子树且左子树路径上编码为0,指定权值次小的在右子树且右子树路径上编码为1,以此类推,构成哈夫曼树,运用变量或容器分别记录下相应的值。
代码实现:
#include<stdio.h>
#include<iostream>
#include<queue>
//C++ STL队列queue的头文件,是STL中实现的一个先进先出的容器
#include<vector>
//是一个封装了动态大小数组的顺序容器
#include<string>
//C++标准库中的头文件,它包含了用于操作字符串的类和相关的函数
#include<map>
//STL(中文标准模板库)的一个关联容器,可以将任何基本类型映射到任何基本类型
using namespace std;
#define MAX 101//宏定义,最多结点个数
int n;//结点
struct HTreeNode//哈夫曼树结点类型
{
char data;//字符
int weight;//权值
int parent,lchild,rchild;//双亲位置、左孩子位置、右孩子位置
};
HTreeNode ht[MAX];//存放哈夫曼树
map<char,string>htcode;//存放哈夫曼编码
struct NodeType//优先队列结点类型
{
int no;//对应哈夫曼树ht中的位置
char data;//字符
int weight;//权值
bool operator<(const NodeType &s)const
//重载<关系函数
//类成员函数,判断当前对象是否小于另一个给定的NodeType对象s
//访问对象成员变量比较大小
{
return s.weight<weight;
//用于创建小根堆,权值weight越小越优先出队
}
};
void CreateHTree()//构建哈夫曼树
{
NodeType e,e1,e2;//定义3个结点
priority_queue<NodeType>qu;//定义优先队列
for(int k=0;k<2*n-1;k++)
//设置所有结点的指针域,指针域是结点中存储数据元素之间的链接信息即下一个结点地址的部分。
ht[k].lchild=ht[k].rchild=ht[k].parent=-1;
for(int i=0;i<n;i++)//将n个结点进队
{
e.no=i;//e结点对应哈夫曼树ht中的位置
e.data=ht[i].data;//e结点的字符
e.weight=ht[i].weight;//e结点的权值
qu.push(e);//结点e进队
}
for(int j=n;j<2*n-1;j++)//构造哈夫曼树的n-1个非叶子结点
{
e1=qu.top();//top优先队列(priority_queue)的顶部元素的函数
qu.pop();//出队权值最小的结点e1
e2=qu.top();//top优先队列(priority_queue)的顶部元素的函数
qu.pop();//出队权值次小的结点e2
ht[j].weight=e1.weight+e2.weight;//构造哈夫曼树的非叶子结点
ht[j].lchild=e1.no;//结点j左孩子位置对应哈夫曼树ht中的位置
ht[j].rchild=e2.no;//结点j右孩子位置对应哈夫曼树ht中的位置
ht[e1.no].parent=j;//修改e1.no的双亲为结点j
ht[e2.no].parent=j;//修改e2.no的双亲为结点j
e.no=j;//构造队列结点e
e.weight=e1.weight+e2.weight;//队列结点e权值
qu.push(e);//结点e进队
}
}
void CreateHCode()//构造哈夫曼编码
{
string code;//生成空字符串
code.reserve(MAX);//编码容量
//reserve:预先设定容量到指定值,背后执行的可能是内存分配
for(int i=0;i<n;i++)//构造叶子结点i的哈夫曼编码
{
code="";//编码
int curno=i;//结点i
int f=ht[curno].parent;//双亲f
while(f!=-1)//循环到根结点
{
if(ht[f].lchild==curno)//curno为双亲f的左孩子
code='0'+code;//左子树路径上编码为0
else//curno为双亲f的右孩子
code='1'+code;//右子树路径上编码为1
curno=f;//curno为双亲f
f=ht[curno].parent;//双亲f
}
htcode[ht[i].data]=code;//得到ht[i].data字符的哈夫曼编码
}
}
void DispHCode()//输出哈夫曼编码
{
map<char,string>::iterator it;
//字符串到字符串的映射,迭代器
for(it=htcode.begin();it!=htcode.end();++it)
cout<<" "<<it->first<<":"<<it->second<<endl;
//通过迭代器进行访问,map可以使用it->first来访问键(下标),使用it->second访问值
}
void DispHTree()//输出哈夫曼树
{
for(int i=0;i<2*n-1;i++)
{
printf(" data=%c, weight=%d, lchild=%d, rchild=%d, parent=%d\n",
ht[i].data,ht[i].weight,ht[i].lchild,ht[i].rchild,ht[i].parent);
//哈夫曼树的字符、权值、左孩子位置、右孩子位置、双亲位置
}
}
int WPL()
//求WPL带权路径长度
{
int wps=0;
for(int i=0;i<n;i++)
wps+=ht[i].weight*htcode[ht[i].data].size();
//带权路径长度=从根结点到各个叶子结点的路径长度与相应结点权值的乘积的和
return wps;
}
int main()
{
n=5;
ht[0].data='a';//置初值,即n个叶子结点
ht[0].weight=4;
ht[1].data='b';
ht[1].weight=2;
ht[2].data='c';
ht[2].weight=1;
ht[3].data='d';
ht[3].weight=7;
ht[4].data='e';
ht[4].weight=3;
CreateHTree();//建立哈夫曼树
printf("构造的哈夫曼树:\n");
DispHTree();//输出哈夫曼树
CreateHCode();//求哈夫曼编码
printf("产生的哈夫曼编码如下:\n");
DispHCode();//输出哈夫曼编码
printf("带权路径长度:\n WPL=%d\n",WPL());
}
程序测试:
![]() |
n=6;
ht[0].data='a';//置初值,即n个叶子结点
ht[0].weight=5;
ht[1].data='b';
ht[1].weight=9;
ht[2].data='c';
ht[2].weight=2;
ht[3].data='d';
ht[3].weight=6;
ht[4].data='e';
ht[4].weight=1;
ht[5].data='f';
ht[5].weight=8;
如需源文件,请私信作者,无偿