最小重量机器设计问题(回溯-分支限界)

一、题目

★问题描述:设某一机器由 n n n个部件组成,每一种部件都可以从 m m m个不同的供应商处购得。设 w ij w_{\text{ij}} wij是从供应商 j j j处购得的部件 i i i的重量, c ij c_{\text{ij}} cij是相应的价格。

试设计一个算法,给出总价格不超过 c c c的最小重量机器设计。

★算法设计:对于给定的机器部件重量和机器部件价格,计算总价格不超过 d d d的最小机器重量设计。

★数据输入:由文件input.txt给出输入数据。第一行有3个正整数 n , m , d n,m,d n,m,d。接下来的2 n n n行,每行 n n n个数。前 n n n行是 c c c,后 n n n行是 w w w

★结果输出:将计算的最小重量及每个部件的供应商输出到文件output.txt。

输入文件示例 输出文件示例

input.txt
3 3 4
1 2 3
3 2 1
2 2 2
1 2 3
3 2 1
2 2 2

output.txt
4
1 3 1

二、题目解析

1、设计分析

题目是一个组合优化问题,解决此类问题可以考虑使用贪心算法、动态规划、回溯与分支限界算法。对于本题,如果使用贪心算法,每种部件都选择重量最小的供应商,显然会因为没有考虑价格而导致无法正确求解。另外,由于本题不符合优化原则即当选择 n n n种部件时的最优解不一定是选择 n + 1 n + 1 n+1种部件时的最优解,所以不能使用动态规划算法。由于本题符合多米诺性质即如果某一选择方案选择了 n n n个部件时不符合约束条件,那么无论第 n + k ( k > 0 ) n + k(k > 0) n+k(k>0)种部件如何选择,该选择方案都不符合约束条件,所以可以使用回溯算法,同时为了加快回溯速度,以下解法采用了分支限界技术。

解题过程: n n n种部件, m m m个供应商,总价格不超过 d d d,设每个供应商的编号分别为 1 , 2 , 3 ⋅ ⋅ ⋅ , m 1,2,3 \cdot \cdot \cdot ,m 123m

搜索空间为 { < x 1 , x 2 , x 3 , ⋅ ⋅ ⋅ x n >   ∣   x i ∈ N , 1 ≤ x i ≤ m } \left\{ < x_{1},x_{2},x_{3}, \cdot \cdot \cdot x_{n} > \ \right|\ x_{i} \in N,1 \leq x_{i} \leq m\} {<x1,x2,x3,xn>  xiN,1xim} x i x_{i} xi的值表示第 i i i个部件的供应商编号。

约束条件:所有部件的总价格 ≤ d \leq d d.

界: 当前已经得出的所有可行解对应的重量的最小值

代价函数: 当前已经选择的部件总重量 + + +剩余部件(不考虑价格)可能的最小重量即结

< x 1 , x 2 , x 3 , ⋅ ⋅ ⋅ , x k > < x_{1},x_{2},x_{3}, \cdot \cdot \cdot ,x_{k} > <x1,x2,x3,,xk>处代价函数的值为

F k = ∑ i = 1 k − 1 c i x i + ∑ i = k n w i ′ F_{k} = \sum_{i = 1}^{k - 1}c_{ix_{i}} + \sum_{i = k}^{n}w_{i}^{'} Fk=i=1k1cixi+i=knwi

其中 c ij c_{\text{ij}} cij为第 i i i个部件从第 j j j个供应商处购买的价格, w i ′ w_{i}^{'} wi为第 i i i个部件的最低价格

搜索策略:深度优先

在根结点处,没有选择任何部件,总重量和总价格都为 0 0 0,代价函数的值为所有部件的最低价格之和,界函数的初值为无穷大,代价函数小于界函数,所以往下搜索根结点的 m m m个分支。对于所有结点,都进行如下操作:

①判断当前结点代价函数的值是否大于界函数,如果是,则回溯。

②(分支限界)判断是否满足约束条件,如果不满足则回溯。

③如果在①和②处都没有回溯,则继续向下搜索 m m m个分支

如果搜索到叶子结点,即得到一个可行解,则将其与界函数比较,如果可行解对应的总重量小于界函数,则将界函数的值更新为该可行解对于的总重量,记录对于的供应商选择方案,然后回溯。

实例:
在这里插入图片描述
源代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
/*
n:部件数量
m:供应商数量
d:最大总价格
使用分支限界算法
	搜索策略:深度优先
	搜索树:树中的结点可表示为序列<X1,X2,X3···,Xn>,其
中Xi表示第i个部件选择的供应商编号(1<=Xi<=m)
	约束条件:总价格小于d
	界:当前已经求出的总价格<=d的最小重量和
	代价函数:当前已经选择的部件总重量+剩余部件(不考虑价格)可能的最小重量
*/
#define MAX 100
int cost[MAX][MAX],weight[MAX][MAX],minSum[MAX];
int result[MAX], tempResult[MAX];
int n, m, d, minWeight = 99999, curWeight = 0, curCost = 0;
/*
cost[i][j]:第i个部件从第j个供应商购买的价格
weight[i][j]:第i个部件从第j个供应商购买的重量
minSum[i]:不考虑价格,后(n-i)个部件的最小重量和

result[i]:储存第i个部件的供应商编号
tempResult[i]:在求解过程中储存第i个部件的供应商编号

n:部件的总数
m:供应商数量
minWeight:最小重量(目标函数)
curWeight:记录当前(已选部件)的总重量
curCost:记录当前(已选部件)的总价格
*/
void setMinSum()//计算minSum数组的值
{
	//minSum[i]:不考虑价格,后(n - i)个部件的最小重量和
	
	for (int i = n; i >=1; i--)
	{
		//后(n - i)个部件的最小重量和=后(n - i-1)个部件的最小重量和+第(n-i)个部件的最小重量
		minSum[i]=minSum[i+1]+ *(min_element(weight[i]+1, weight[i] + 1+m));
		/*printf("w%d:%d\n ",i, *(weight[i] + 3));
		printf("min:%d\n ", *(min_element(weight[i] + 1, weight[i] + m)));
		printf("sum:%d \n", minSum[i]);*/
	}
}

void Reback(int k)//求价格<=d的第k~n个部件最小重量和
{
	/*
	从搜索树的第k层开始搜索
	*/
	if (k == 1)
	{
		/*
		从第1层搜索时先初始化MinSum函数,便于
		之后计算代价函数
		*/
		setMinSum();
	}
	if (k > n)//到达叶子结点(所有部件都已选择完毕)
	{
		if (curWeight < minWeight)//当前的重量和<已经求出的的最小重量和
		{
			minWeight = curWeight;//更新最小重量和
			for (int i = 1; i <= n; i++)
			{
				result[i] = tempResult[i];//更新每个部件的供应商编号
			}
		}
		return;//到达叶子结点,回溯
	}
	else
	{
		if (minSum[k] + curWeight > minWeight)
		{
			
			/*
			代价函数:剩余部件的最小重量和+当前已选部件重量和
			界函数:当前已经求出的最小重量和

			当代价函数>界函数,直接回溯
			*/
			return;
		}
		for (int i = 1; i <= m; i++)//循环搜索选择第m个供应商的情况
		{
			if (curCost + cost[k][i] <= d)//如果总价格小于d
			{
				curWeight += weight[k][i];//当前总重量加上第k个部件从第i个供应商购买的重量
				curCost += cost[k][i];//当前总价格加上第k个部件从的第i个供应商购买的价格
				tempResult[k] = i;//记录第k个部件的供应商编号
				
				Reback(k + 1);//递归,求价格<=d的第k+1~n个部件最小重量搜索
				
				//在下一次循环前先将重量和价格恢复原状
				curWeight -= weight[k][i];
				curCost -= cost[k][i];

			}
		}



	}
}

int main()
{
	FILE* fr = fopen("./input.txt", "r");
	fscanf(fr, "%d%d%d", &n, &m, &d);//从input.txt中读取n,m,d
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			fscanf(fr,"%d", &cost[i][j]);//读取第i个部件从的j个供应商购买的价格
		}
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			fscanf(fr,"%d", &weight[i][j]);//读取第i个部件从的j个供应商购买的重量
			//printf("%d%d :%d   ",i,j,weight[i][j]);
		}
	}
	fclose(fr);

	Reback(1);//求价格<=d的1~n个部件的最小重量和
	FILE* fw = fopen("./output.txt", "w");
	fprintf(fw, "%d\n", minWeight);//将最小重量和输出到output.txt
	for (int i = 1; i <= n; i++)
	{
		//将每个部件的供应商编号输出的output.txt
		fprintf(fw,"%d ", result[i]);
	}
	fclose(fw);

	//打印结果
	printf("最小重量: %d\n", minWeight);
	printf("各部件供应商:");
	for (int i = 1; i <= n; i++)
	{
		printf("%d ", result[i]);
	}
	printf("\n");

	return 0;
}

三、测试实例

input-1
8 18 14
18 15 20 5 15 10 16 6 1 6 17 6 1 2 17 15 13 17
16 6 7 4 7 2 11 6 18 4 13 12 8 5 2 8 15 14
12 6 19 10 13 8 2 10 16 4 15 15 16 13 17 12 14 4
18 18 2 13 15 19 5 12 18 7 13 9 8 17 10 13 15 11
8 5 14 11 18 20 17 3 11 17 13 11 4 9 17 14 19 1
10 7 8 11 13 3 19 3 12 11 12 14 4 2 12 10 14 15
12 9 13 9 16 17 12 15 6 3 11 17 13 17 14 13 4 4
19 12 3 19 3 20 19 12 8 19 8 10 19 20 3 1 7 1
16 12 4 16 2 6 15 1 13 3 7 16 5 3 16 16 14 19
12 14 6 2 11 15 9 17 15 16 19 20 14 14 20 9 4 4
6 13 16 6 3 12 12 19 11 20 4 13 9 18 7 17 8 1
4 17 3 20 3 8 12 7 4 12 6 12 1 18 13 20 20 8
4 15 1 10 2 12 8 11 5 4 20 13 12 20 1 3 3 11
1 9 2 1 16 1 12 4 5 2 7 15 12 3 9 4 13 6
13 1 10 8 5 13 20 10 6 4 8 15 8 8 20 11 9 9
2 10 11 1 18 8 20 11 18 2 3 6 14 16 19 4 3 15
output-1
57
13 6 7 3 18 14 10 16

intput-2(贪心算法会出错)
3 3 6
1 2 3
2 4 1
2 2 2
1 2 3
3 1 2
2 2 2
output-2
5
1 3 1

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值