算法第五章上机实践报告

一、题目描述

7-2 最小重量机器设计问题 (25 分)

设某一机器由n个部件组成,每一种部件都可以从m个不同的供应商处购得。设wij是从供应商j 处购得的部件i的重量,cij是相应的价格。 试设计一个算法,给出总价格不超过d的最小重量机器设计。

输入格式:

第一行有3 个正整数n ,m和d, 0<n<30, 0<m<30, 接下来的2n 行,每行n个数。前n行是c,后n行是w。

输出格式:

输出计算出的最小重量,以及每个部件的供应商

输入样例:

3 3 4
1 2 3
3 2 1
2 2 2
1 2 3
3 2 1
2 2 2

输出样例:

在这里给出相应的输出。例如:

4
1 3 1 

  

二、算法分析

1. 请用回溯法的方法分析“最小重量机器设计问题”

每个零件有m个供应商可以选择,这样有m^n种可选方案,其中就有问题想要的最优解。然后把解空间组织成一棵m叉树,然后以深度优先搜索的方式纵深向下搜索,当不满足约束函数(即约束条件d)或者是限界函数(即搜索过的可行方案中最小的质量总和)时回溯,寻求尝试另一种方案,然后继续纵深向下搜索。当向下搜索到叶子节点时,不再向下搜索,转去更新最优值。

 

1.1说明“最小重量机器设计问题"的解空间

解空间是穷举所有可能解的空间,{(1,3,1),(1,3,2),(1,3,3)}

 

1.2 说明“最小重量机器设计问题"的解空间树

解空间树是一棵m叉树,从根节点出发后,每一层分别表示一个部件的不同供应商,往下延伸共n层。

 

1.3 在遍历解空间树的过程中,每个结点的状态值是什么

每个结点有两个状态值,分别是当前的总价格、当前的总重量。

 

三、问题求解代码

#include<iostream>
using namespace std;

int n,m,d;//n个组件 m个供应商 总价格不超过d
int c[999][999],w[999][999];//c[i][j],w[i][j] 各表示从供应商j处购得部件i的价格/重量
int cw=0,cp=0;//记录当前最小的重量和价值
int bestw=999,bestp=999;//最小重量
int x[999],bestx[999];//保存当前重量下每个部件供应商的号码
 
void backtrack(int i) 
{
	if(i>n) 
	{
           if(cp<=d&&cw<bestw) 
        { //条件更优时,更新数据
               bestw=cw;
               bestp=cp;
               for(int j=1; j<=n; j++)
               {
           	    bestx[j]=x[j];
	       }
           }
       } 
	else 
	{
		for(int j=1; j<=m; j++) 
		{
		  x[i]=j;//记录下当前部件是哪一个供应商
		  cw=cw+w[i][j];
		  cp=cp+c[i][j];
		  if(cp<=d&&cw<bestw) 
		  {//剪枝,如果当前供应商的部件可以满足条件 直接进入下一个部件的判断
                    backtrack(i+1);
           	  }
           	//回溯过程
           	cw=cw-w[i][j];
           	cp=cp-c[i][j];
       }
    }
}

 
int main() 
{
    cin>>n>>m>>d;
    for(int i=1; i<=n; i++)
  {
	for(int j=1; j<=m; j++)
	{
	  cin>>c[i][j];
	 }
  }
   for(int i=1; i<=n; i++)
  {
       for(int j=1; j<=m; j++)
	{
           cin>>w[i][j];
	}
  }
    backtrack(1);
    cout<<bestw<<endl;
    for(int i=1; i<=n; i++)
  {
       cout<<bestx[i]<<" ";
  }
    return 0;
}

  

四、心得体会

对回溯算法的理解

1.回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。

2.回溯法的基本步骤: (1)针对所给问题,定义问题的解空间; (2)确定易于搜索的解空间结构; (3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

3.常用剪枝函数:  (1)用约束函数在扩展结点处剪去不满足约束的子树; (2) 用限界函数剪去得不到最优解的子树。

4.通解模板如下:

void backtrack (int t)  //回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法
{
   if (t>n) output(x);  //算法已搜索到一个叶结点,输出该结果
   else
     for (int i=f(n,t);i<=g(n,t);i++)   //f(n,t),g(n,t)分别表示扩展结点的子树的起始编号和终止编号。尝试这些选择
     {  
        x[t]=h(i);  //H(i)表示第i个可选值
        if (constraint(t)&&bound(t))   //分别表示约束函数和限界函数
        backtrack(t+1);
   }
}

5. 在用回溯法求解问题时,常常遇到两种典型的解空间树:
  ①子集树:当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树成为子集树。例:0-1背包问题。

遍历子集树函数:

void Backtrack(int t)
{//以深度优先的方式遍历第t层中的某棵子树
	if(t>n) { Output(x); return; }
	if (……) 
  {  x[t]=1; Backtrack(t+1); }
	if (……) 
  {  x[t]=0; Backtrack(t+1); }
}

  ②排列树:当所给问题是确定n个元素的满足某种性质的排列时,相应的解空间树称为排列树。例:旅行售货员问题。

遍历排列树函数:

void Swap(int *p,int *q)
{	int temp;
	temp=*p;*p=*q;*q=temp; }
void Backtrack(int t) 
{  //遍历第t层结点为根的某棵子树
   int i;
	if(t>n) { Output(x); return; }	
	for(i=t;i<=n;i++)
   //x[1]~x[t-1]确定,获得x[t]~x[n]的不同组合
		if(……) 
     {   Swap(&x[t],&x[i]); 
         Backtrack(t+1);
		   Swap(&x[t],&x[i]);	}
}

  

posted @ 2021-12-14 23:07  张力元  阅读(54)  评论(0编辑  收藏  举报