一、题目
★问题描述:设某一机器由 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 1,2,3⋅⋅⋅,m,
搜索空间为 { < 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> ∣ xi∈N,1≤xi≤m}, 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=1k−1cixi+∑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