1.先说一下顺序队列来建立数据结构
/************************************************************************
顺序队列(循环队列)实现FIFO分支限界法--装载问题把数据结构换了一下,
仅仅把之前的链队列换成了循环队列。其余的函数基本没有变,还有main()函
数根本就没变,只是EnQueue(), Add(), DeQueue()这些涉及到E和bestE的函
数中的某些函数参数由QUEUE *类型变成了int类型。函数调用中的参数根本就
没变,也不用变。在原来的链队列程序中,数据结构定义按照节点来的,每个
节点中有*parent, weigth, LChild, *next。然后一个队列是很多个节点,每次
入队或者出队通过加入含有这些类型元素的节点或者删除,E和bestE是独立的,
不依赖于链队列LQ的具有以上所说类型的节点,每次用来存放某些节点的地址,
只要找到了某个节点地址,就可以调用这个节点里面的*parent, weigth, LChild
等元素,这是链队列的来实现的思想。这里我采用循环队列来实现,顺序队列也
好,循环队列也好,都是顺序的存储方式,即将要存储的数据存到数组里面(数组
是顺序的)。所以思想就跟之前的链队列实现的思想有所不同,原来每个节点有一
个parent, weight,LChild之类的,但是如今只有一个节点SQ。元素的寻找是通过
下标值来寻找也就相当于模拟链队列里面的节点地址,这个下标值可以模拟成地
址。so,weight, parent, LChild每个都是一个数组weight[], parent[], LChild[];
比如链队列中的E->weight表示的是E所指向的那个节点的weight,在循环队列
里面就要用weight[E]来实现了,在链队列中,E是QUEUE*型的,表示的是队列
节点的那种类型,然而在顺序队列里面,就定义成了int型,表示的是下标值,从
1开始(0表示加入空节点weight值为-1时)。再比如,原来的链队列程序中
bestx[j] = bestE->LChild ;bestE = bestE ->parent ;换成了下面的:
bestx[j] = Q->LChild[bestE] ; bestE = Q->parent[bestE] ;so,可以仔细体味
一下这两种方式的不同。
其实还有一种构建数据结构的方式,也是循环队列或者顺序队列,总是是用数组来
存储元素的。但是那种方式和链队列的思想基本上是一样,按照开辟节点的方式,
唯一不同的是链队列的节点之间不是顺序的,而是通过指针连起来的,而此种方式
确实开辟一个顺序的节点空间Q[100];见下面
struct
{
int weight;
int LChild;
int parent;
}Q[MAXSIZE];
int front, int rear;
这个和链队列方式基本一样weight, LChild, parent都是节点内部的元素。而front,
rear都是独立的元素,用来指向节点。仔细体味三种队列构建方式的异同。
*******************************************************************************/
#include
#include
#define MAXSIZE 100
typedef struct
{
int weight[MAXSIZE];
int LChild[MAXSIZE];
int parent[MAXSIZE];
int front, rear;
}SEQUEUE;
//只有个全局变量:*bestx,bestw。
int *bestx ;
int bestw = 0 ; // 目前的最优值
void InitQueue(SEQUEUE *SQ)
{
SQ->front=SQ->rear=0;
for(int i=0;iparent[i]=-1;
}
}
int Empty(SEQUEUE *SQ)
{
if(SQ->rear==SQ->front)
return 1;
else
return 0;
}
int Add(SEQUEUE *SQ, int w, int E, int l)
{
if((SQ->rear+1)%MAXSIZE==SQ->front)
{
printf("队列满了!\n");
return 0;
}
SQ->rear=(SQ->rear+1)%MAXSIZE;
SQ->weight[SQ->rear]=w;
SQ->LChild[SQ->rear]=l;
SQ->parent[SQ->rear]=E;
return 1;
}
int DeQueue(SEQUEUE *SQ, int *E)
{
if(Empty(SQ))
{
printf("队列空了!\n");
return 0;
}
SQ->front=(SQ->front+1)%MAXSIZE;
*E=SQ->front;
return 1;
}
void EnQueue(SEQUEUE *SQ, int wt, int i, int n , int E , int *bestE , int ch)
{
if(i==n)
{
if(wt==bestw)
{
*bestE = E;//bestE只有i==n时候,也就是叶子的时候,才用得到bestE,因为为了追溯路径,靠bestE=bestE->parent;也就是说一旦到了叶子节点,说明搜索已经结束
bestx[n] = ch;
}
return;
}
Add(SQ, wt , E, ch); // 不是叶子
}
/*
int GetHeadQueue(SEQUEUE *SQ, int *x)
{
if(Empty(SQ))
{
printf("队列空了!\n");
return 0;
}
*x=SQ->data[(SQ->front+1)%MAXSIZE];
return 1;
}
*/
int MaxLoading(int w[], int c, int n)//求最大装载函数
{
int err ; //返回值
int i = 1; // 当前扩展结点的层
int cnt=0;
int Ew = 0; // 当前扩展结点的权值
int r = 0 ; //剩余集装箱重量
int flag=0;
int E=0;
int bestE=0;
SEQUEUE *Q=(SEQUEUE *)malloc(sizeof(SEQUEUE)); // 活结点队列
for(int j =2; j<=n; j++)
{
r += w[j];
}
bestw = 0; // 目前的最优值
InitQueue(Q);
err=Add(Q, -1, 0, 0);
if(!err)
{
return 0 ;
}
while (true)
{
int wt = Ew + w[i] ;
if (wt <= c) // 检查左孩子结点
{
if(wt>bestw)
bestw = wt ;// x[i] = 1
EnQueue(Q, Ew + w[i], i , n , E , &bestE , 1);
flag=1;
/******这部分剪枝的内容也可以放在这个里面,当然下面就只有if(!flag),而没有else了***
if(Ew+r>=bestw&&ibestw&&i当前最优值bestw
//(bestw一般来讲就是当前的右子树的同一级的左节点的值),如果不大于,说明本级的下面几级的子树即使全部取左子树(每个物品都要)
//这样也不如当前的值bestw大,所以此右子树就不必要了,所以对右子树进行判断剪枝(满足if里面的条件就不剪枝,右孩子入队)。
}
DeQueue(Q, &E);
if (E!=0&&Q->weight[E] == -1) //我觉得这个地方E!=NULL没啥用,直接if(E->weight==-1)就行
{// 到达层的尾部
if (Empty(Q))
{
break;
}
if(iweight[E];
}
for(int j = n-1; j>0; j--)//构造当前最优解
{
bestx[j] = Q->LChild[bestE] ;
bestE = Q->parent[bestE] ;
}
return 0 ;
}
int main()
{
int n =0 ;
int c = 0 ;
int i = 0 ;
int* w ;
FILE *in , *out ;
in = fopen("input4.txt" , "r") ;
if(in==NULL)
{
printf("没有输入输出文件\n") ;
return 1 ;
}
fscanf(in , "%d" , &n) ;
fscanf(in , "%d" , &c) ;
w = (int*)malloc(sizeof(int)*(n+1)) ;
bestx = (int*)malloc(sizeof(int)*(n+1)) ;
for(i =1 ; i<=n ; i++)
{
fscanf(in , "%d" , &w[i]) ;
}
MaxLoading(w , c , n) ;
out = fopen("output.txt" , "w") ;
for(i=1 ; i<=n ; i++)
{
fprintf(out , "%d\t" , bestx[i]) ;
}
fprintf(out , "\n") ;
fclose(in);
fclose(out);
return 0 ;
}
二.再说一下链队列来建立数据结构
/************************************************************
二重指针方式解决FIFO队列装载问题v0.3版本,全部采用
局部变量+二重指针+正规熟悉的队列构建方式+正确的剪枝算法
输入:在input.txt中输入n,c,w[1], w[2], ......, w[n]。
输出:输出到output.txt中bestx[1], bestx[2], ......, bestx[n]的值
为表示w[i]被选中,为表示没有被选中。
数据结构:队列。应用了自己定义的规范型的通用的队列实现方式,
并且灵活做了个性化处理,比如*parent, LChild等
*************************************************************/
#include
#include
typedef struct Qnode{
struct Qnode *parent; // 指向父结点的指针
struct Qnode* next ;
int LChild; // 左儿子标志
int weight; // 结点所相应的载重量
}QTYPE;
/*注意:再定义*E, *bestE的时候直接用QTYPE定义,因为E和bestE都是QTYPE型的,不用定义成LinkQUEUE型的。E和bestE是个游离的节点,与LinkQUEUE *Q的Q没直接关系,不依赖与Q,是自由的,但是front 和rear是在LinkQUEUE里面定义的,虽然是QTYPE类型的,但是依赖于Q。Q->front, Q->rear.
*/
typedef struct
{ //
QTYPE *front, *rear;
}LinkQUEUE; //
//只有2个全局变量:*bestx,bestw。
int *bestx ;
int bestw = 0 ; // 目前的最优值
int InitQueue(LinkQUEUE *LQ)//初始化队列
{
QTYPE *p;
p=(QTYPE *)malloc(sizeof(QTYPE));
if(!p)
{
printf(" 分配空间失败!\n");
return 0;
}
p->next=NULL;
p->weight=-1;
p->parent=NULL;
LQ->front=LQ->rear=p;
return 1;
}
int Add(LinkQUEUE *LQ, int w, QTYPE *E, int l)//入队的原子操作
{
QTYPE *q;
q=(QTYPE *)malloc(sizeof(QTYPE));
if(!q)
{
printf("分配空间失败!\n");
return 0;
}
q->weight=w;//
q->next=NULL;
q->parent=E;//
q->LChild=l;//
LQ->rear->next=q;
LQ->rear=q;
return 1;
}
int IsEmpty(LinkQUEUE *LQ)//判断对空
{
return (LQ->front==LQ->rear?1:0);
}
/*出队,出队的节点指针赋给*E,通过二重指针方式来修改传递的参数*/
int Delete(LinkQUEUE *LQ, QTYPE **E)
{
QTYPE *tmp=NULL;
if(IsEmpty(LQ))
{
printf("队空!\n");
return 0;
}
tmp=LQ->front->next;
LQ->front->next=tmp->next;
if(LQ->front->next==NULL)
LQ->rear=LQ->front;
*E=tmp;
//free(tmp);
return 1;
}
/*入队的封装操作通过二重指针方式来修改传递的参数*/
void EnQueue(LinkQUEUE *LQ, int wt,int i, int n , QTYPE *E ,QTYPE **bestE , int ch) {
if(i==n)
{
if(wt==bestw)
{
*bestE = E;//bestE只有i==n时候,也就是叶子的时候,才用得到bestE,因为为
//了追溯路径,靠bestE=bestE->parent;
//也就是说一旦到了叶子节点,说明搜索已经结束
bestx[n] = ch;
}
return;
}
Add(LQ, wt , E, ch); // 不是叶子
}
int MaxLoading(int w[], int c, int n)//求最大装载函数
{
int err ; //返回值
int i = 1; // 当前扩展结点的层
int cnt=0;
int Ew = 0; // 当前扩展结点的权值
int r = 0 ; //剩余集装箱重量
int flag=0;
LinkQUEUE *Q=(LinkQUEUE *)malloc(sizeof(LinkQUEUE)); // 活结点队列
QTYPE *E=NULL;
QTYPE *bestE=NULL;
for(int j =2; j<=n; j++)
{
r += w[j];
}
bestw = 0; // 目前的最优值
InitQueue(Q);
err=Add(Q, -1, NULL, 0);
if(!err)
{
return 0 ;
}
while (true)
{
int wt = Ew + w[i] ;
if (wt <= c) // 检查左孩子结点
{
if(wt>bestw)
bestw = wt ;// x[i] = 1
EnQueue(Q, Ew + w[i], i , n , E , &bestE , 1);
flag=1;
/***这部分剪枝的内容也可以放在这个里面,当然下面就只有if(!flag),而没有else了
if(Ew+r>=bestw&&ibestw&&i当前最优值
//bestw(bestw一般来讲就是当前的右子树的同一级的左节点的值),如果不
//大于,说明本级的下面几级的子树即使全部取左子树(每个物品都要)
//这样也不如当前的值bestw大,所以此右子树就不必要了,所以对右子树进
//行判断剪枝(满足if里面的条件就不剪枝,右孩子入队)。
}
Delete(Q, &E);
if (E!=NULL&&E->weight == -1) //我觉得这个地方E!=NULL没啥用,直接
//if(E->weight==-1)就行
{// 到达层的尾部
if (IsEmpty(Q))
{
break;
}
if(iweight ;
}
for(int j = n-1; j>0; j--)//构造当前最优解
{
bestx[j] = bestE->LChild ;
bestE = bestE ->parent ;
}
return 0 ;
}
int main()
{
int n =0 ;
int c = 0 ;
int i = 0 ;
int* w ;
FILE *in , *out ;
in = fopen("input.txt" , "r") ;
if(in==NULL)
{
printf("没有输入输出文件\n") ;
return 1 ;
}
fscanf(in , "%d" , &n) ;
fscanf(in , "%d" , &c) ;
w = (int*)malloc(sizeof(int)*(n+1)) ;
bestx = (int*)malloc(sizeof(int)*(n+1)) ;
for(i =1 ; i<=n ; i++)
{
fscanf(in , "%d" , &w[i]) ;
}
MaxLoading(w , c , n) ;
out = fopen("output.txt" , "w") ;
for(i=1 ; i<=n ; i++)
{
fprintf(out , "%d\t" , bestx[i]) ;
}
fprintf(out , "\n") ;
fclose(in);
fclose(out);
return 0 ;
}
三.
运行结果:
在input.txt中输入:n=4, c=70, w1=30, w2=25, w3=15, w4=10.
在output.txt中查看运行结果:
这个1 1 1 0是bestx[i]的结果,bestx[i]==1代表选择这个分支,也就是第i个货物装船,为0表示不装船。