贪心算法–装箱问题
还在持续学习中,如有什么纰漏,请在评论区加以指正……
简单认识贪心算法
贪心算法,在我最初的理解里就是贪心,什么都想要最好的。那么,这就正符合了贪心算法在运用过程中的特点–即贪心算法总是做出当前看来是最好的选择。这就意味着贪心算法并不从整体最优上考虑,而只是在当前情况下的最优选择。
贪心准则
- 贪心准则是指在解决问题的过程中将解决这一个问题的n个步骤的每一步都抽象出其公共的最优解。
注:这就是贪心算法所阐述的要求每一步都最优,每一步都能得到当前情况下的最优解。
- 一个问题中只有一个贪心准则。
注:那就意味着只要在解决问题过程中有其中一步解决问题的办法与前面不同,那就得推翻这一个贪心准则,重新找出解决问题步骤的公共最优解决办法。
总而言之呢,贪心算法最大的特点就是在解决问题的时候最简单,最快捷,最高效的得到一个近似最优解的结果。
装箱问题
接下来,以一个典型的装箱问题来进一步理解一下贪心算法。
问题描述:
- 假设有n个物品,其体积为:V1,V2,V3……Vn;
- 有若干个体积为V的箱子。
要求:将这n个物品全装到箱子里,使得打开的箱子尽可能地少。
!!!注意哈,要求上说使得打开的箱子尽可能地少,而不是最少,那么这就是典型的贪心算法可以解决的问题,求出一个近似最优解的解。
思考:我们怎样才能合理化分配箱子的空间使得装进的物品尽可能多,从而打开的箱子也就相应的尽可能地少呢?第一反应肯定是尽着大的装,所以我们首先要对物品进行排序,使得体积大的物品排在前面。第一个箱子装的是最大体积的物品,那么第二个物品要再装箱的时候就要进行比较。第一个箱子的剩余空间装得下这第二个物品吗?装得下我们就直接装进去,装不下我们就只能再重新打开一个箱子。第三个物品跟第二个物品装箱的步骤一样,从第一个箱子开始遍历,第一个箱子剩下的空间够不够,够的话就装,不够就看第二个箱子够不够,够的话就装,不够就要打开新的箱子了。依次类推,第n个物品装箱的时候依旧从第一个箱子开始遍历,剩余的空间够不够呀,够的话就装进去,不够,下一个进行比较,直到所有的箱子的剩余空间都不够的时候就打开新的箱子。
算法描述:
1.存储结构
我们在考虑存储结构的时候一定要先对问题对象进行抽象化,就拿这个问题来讲,我们有n个物品,若干个箱子,那么简单直接的就可以看出一定会有物品和箱子这两种类型。那么我们再细化问题:
i> 物品,n个物品则为一个有确定数量的对象,那么我们在存储这类具有确定数量的对象时,就可以采用数组来进行存储,而物品又包括有物品编号,物品的体积,那么这个数组显然就是一个结构体数组。声明如下:
typedef struct node{
int gNum; //物品的编号
int gV; //物品的体积
}Goods;
ii>箱子,若干个箱子,那么这个箱子的数量就不是确定的,所以我们需要采用链表的结构来存储箱子。而箱子又包括箱子剩余的体积,箱子的next指向,箱子装进的物品的编号。
!!!注意了!!!我们只在箱子里存储装入物品的编号,可以直接利用这个编号在物品的信息中找到它所对应的体积。如果将物品所有信息都存储进来,那么就造成了数据的重复存储,从而浪费内存。然而在存储物品编号的时候我们显然不会在这个箱子里只装一个物品,所以,这里就需要另外一个单独的装入物品的类型,我们把它在这里叫做物品编号。这个物品编号的类型里面存储所要装进箱子的物品编号以及它的next的指向。所以,它的声明如下:
typedef struct goodsLink{
int gNum; //物品的编号
struct goodsLink *link; //物品的next指向
}GoodsLink;所以,我们的箱子的声明如下:
typedef struct box{
int restV; //箱子剩余的体积
GoodsLink * hg; //装进的物品的结点
struct box * next;//箱子的next指向
}BoxLink;
综上所述,我们需要有三个存储的对象,声明也已经写过了。接下来我们就要解决问题了。
2.解决过程
i>第一步当然要进行初始化,我们要把每一个物品的信息都写完整。
Goods * g;
g=(Goods *)malloc(N*sizeof(Goods)); //创建一个N个长的数组来存储物品
for(int i=0;i<N;++i){ //在这里我们将物品个数作为全局变量
g[i].gNum=i+1; //物品编号
scanf("%d",&g[i].gV); //手动输入物品的体积
}
ii>在物品信息进行初始化之后,我们要将其进行降序排列,从而使得体积最大的物品在前面。(在这里使用冒泡排序的方法,关于排序,在这里不多赘述。)
我们定义一个void sortD(Goods *g); 的方法,来实现物品按体积大小降序排列。
void sortD(Goods *g){
Goods temp;
for(int i=0;i<N-1;i++){
for(int j=0;j<N-1-i;j++){
if(g[j].gV<g[j+1].gV){
temp=g[j];
g[j]=g[j+1];
g[j+1]=temp;
}
}
}
for (int k = 0; k < N; k++)
printf("第%d号物品的体积为%d\n",g[k].gno,g[k].gv);
}
iii>接下来就是重头戏了–装箱。
a.遍历所有的物品:
for(int i=0;i<N;i++){ //装箱操作 }
b.遍历箱子链
BoxLink *hbox=NULL;
BoxLink *pbox,*tbox;
for(pbox=hbox;pbox&&pbox->restV<g[i].gv;pbox=pbox->next);
我们把当前已经装过物品的箱子都要遍历一遍,只要箱子不为空并且这个箱子的剩余空间小于正要装得这个物品的体积,就让他进行操作。反之,箱子为空并且箱子剩余空间够的时候,那么这个循环就要停下来。停下来的话,我们就要进行判断停下来的时候是因为哪个条件不成立而停下来的呢?如果是箱子是空的,我们就要开新的箱子,然后再把物品装进去。这时候开新箱子的时候要注意,如果是第一个箱子的话就要把开的箱子直接戳上去,如果不是,那就简单了,直接挂链呗,然后再装物品。如果是因为能装下的话那么我们就要把物品装进去。既然不论开箱不开箱都要装物品,我们为何不把装物品这个步骤摘出来呢?
if(!pbox){
//开新箱子
pbox=(BoxLink *)malloc(sizeof(BoxLink));
pbox->restV=V;
pbox->hg=NULL;
pbox->next=NULL;
//这个箱子是不是第一个箱子?
if(!hbox)
hbox=tbox=pbox;
else
tbox=tbox->next=pbox;
}
接下来就要装物品了。装物品我们就要创建一个物品编号的结点从而让它能挂到箱子链上。然后在判断这个箱子是不是一个新的箱子,如果不是新的箱子那么这个箱子里肯定会存放物品,那么他的hg就不为空。
//创建新的物品结点
newg=(GoodsLink *)malloc(sizeof(GoodsLink));
newg->gNum=g[i].gNum;
newg->link=NULL;
//挂链
for(q=pbox->hg;q&&q->link;q=q->link);
if(!q)
pbox->hg=newg;
else
q=newg;
最后我们要重新计算一下箱子的剩余空间。
pbox->restV-=g[i].gV;
所以装箱的整个函数为:
BoxLink * packingBox(Goods *g){
BoxLink *hbox=NULL;
BoxLink *pbox,tbox;
GoodsLink *newg,*q;
//遍历物品
for(int i=0;i<N;i++){
//遍历箱子链
for(pbox=hbox;pbox&&pbox->restV<g[i].gV;pbox=pbox->next);
if(!pbox){
pbox=(BoxLink *)malloc(sizeof(BoxLink));
pbox->restV=V;
pbox->hg=NULL;
pbox->next=NULL;
if(!hbox)
hbox=tbox=pbox;
else
tbox=tbox->next=pbox;
}
//放物品
//i>创建新结点
newg=(GoodsLink *)malloc(sizeof(GoodsLink));
newg->gNum=g[i].gNum;
newg->link=NULL;
//ii>挂链
for(q=pbox->hg;q&&q->link;q=q->link);
if(!q)
pbox->hg=newg;
else
q->link=newg
//重新计算箱子的体积
pbox->restV-=g[i].gV;
}
return hbox;
}
3.装完箱子我们最后肯定要输出让我们看一下结果,毕竟操作了这么久不知道干了个啥也是心塞塞。
void printBox(BoxLink *hbox){
int cnt=0;//用于计算打开箱子的个数
BoxLink *pbox;
GoodsLink *q;
for(pbox=hbox;pbox;pbox=pbox->next){
printf("第%d个箱子:",++cnt);
for(q=pbox->hg;q;q=q->link)
printf("%5d",q->gNum);
printf("\n");
}
}
这下就大功告成啦!!!
完整c语言代码如下,亲测正确!
#include <stdio.h>
#include <stdlib.h>
#define N 5
#define V 20
//物品信息
typedef struct {
int gno; //物品编号
int gv; //物品体积
}EGoods;
//物品结点
typedef struct node{
int gno; //物品编号
struct node * link; //物品结点的指向
}GoodsLink;
//箱子结点
typedef struct box{
int restV; //箱子剩余体积
GoodsLink * hg; //箱子要挂物品结点的指向
struct box *next; //箱子的指向
}BoxLink;
//将数组降序排列
void sortD(EGoods *g){
EGoods temp;
for (int i = 0; i < N-1; i++){
for (int j = 0; j < N - i-1; j++){
if (g[j].gv<g[j+1].gv){
temp = g[j];
g[j] = g[j+1];
g[j+1] = temp;
}
}
}
for (int k = 0; k < N; k++)
printf("第%d号物品的体积为%d\n",g[k].gno,g[k].gv);
}
//装箱
BoxLink * packingBox(EGoods * g){
BoxLink *hbox = NULL;
BoxLink *pbox, *tbox;
GoodsLink *newg,*q;
//遍历所有物品
for (int i = 0; i < N; i++){
//遍历箱子链
for (pbox = hbox; pbox&&pbox->restV<g[i].gv; pbox = pbox->next);
//空箱子则开新箱子
if (!pbox){
pbox = (BoxLink *)malloc(sizeof(BoxLink));
pbox->restV =V;
pbox->hg=NULL;
pbox->next = NULL;
if (!hbox)
hbox = tbox = pbox;
else
tbox = tbox->next = pbox;
}
//放物品
// i>创建物品结点
newg = (GoodsLink *)malloc(sizeof(GoodsLink));
newg->gno = g[i].gno;
newg->link = NULL;
// ii>挂链
for (q = pbox->hg; q&&q->link; q = q->link);
if (!q)
pbox->hg = newg;
else
q->link = newg;
// iii>重置箱子体积
pbox->restV -= g[i].gv;
}
return hbox;
}
//输出计算结果
void printBox(BoxLink *hbox){
int cnt = 0;
BoxLink * pbox;
GoodsLink *q;
for (pbox = hbox; pbox; pbox = pbox->next){
printf("第%d个箱子:\n",++cnt);
for (q = pbox->hg; q; q = q->link)
printf("物品编号为:%d\n",q->gno);
printf("\n");
}
}
int main(void){
//初始化物品信息
EGoods *g = (EGoods *)malloc(N*sizeof(EGoods));
BoxLink *hbox;
for (int i = 0; i < N; ++i){
g[i].gno = i + 1;
printf("请输入物品体积:");
scanf("%d",&g[i].gv);
}
sortD(g);
printf("\n");
hbox=packingBox(g);
printBox(hbox);
return 0;
}