一、实验名称
回溯算法实现题 5-13 工作分配问题:
问题描述:
设有n件工作分配给n个人。将工作i分配给第j个人所需的费用为 c i j。试设计一个算法,为每一个人都分配1件不同的工作,并使总费用达到最小。
设计一个算法,对于给定的工作费用,计算最佳工作分配方案,使总费用达到最小。
二、实验目的
通过上机实验,要求掌握回溯算法求解工作分配问题的问题描述、算法设计思想、程序设计。
三、实验原理
解决回溯算法实现工作分配问题,并计算出程序运行所需要的时间。
四、实验步骤
举例:
输入如下:
输入数据的第一行有1 个正整数n (1≤n≤20)。接下来的n行,每行n个数,表示工作费用。
3
10 2 3
2 3 4
3 4 5
结果为:9
解题思路:
d(i,j)来描述A[1:i]和B[1:j]两字符串之间的扩展距离:
为工作 i 安排第 j 个人的解决方法与“运动员最佳匹配问题”类似,让一方选另一方,这样就可以构成一棵排列树。我们让工作“选”人,那排列树的结点代表人,而层就代表工作。如下图左上角的G1表示工人1,且在第一层,表示为工作 1 安排第 1 个人,即将工作1分配给工人1。
此外,最小值可能有多个,但效果一样,最终返回的都是9。
五、关键代码
1.回溯法求解
//全局变量
long long Min; //因为要求最小值,所以将Min初始化为最大整数(int型)
// 回溯法求解
void dfs(int t,int n,long long &sum,vector<int> &book,vector<vector<int> > &pay)
{
//已经到达叶子结点
if(t>=n) //继续判断是否找到了最小总费用
{
if(Min>sum) //没有找到最小总费用
{
//更新最小总费用
Min=sum;
return;
}
}
for(int i=0;i<n;i++) //为第工作t安排人
{
//第i个人还没有被安排工作
if(!book[i])
{
book[i]=1; //将工作t分配给第i个人
//更新总费用
sum+=pay[t][i];
//如果当前得到的sum小于最小值,就向下搜索子树;否则剪枝
if(sum<Min)
dfs(t+1,n,sum,book,pay);
book[i]=0; //没有得到比Min更小的和,回溯
sum-=pay[t][i];
}
}
}
2.生成不同规模样例:
//生成规模为n的随机数
cout<<"请输入数据规模n:"<<endl;
int n;
cin>>n;
ofstream out("input1.txt");
out<<n<<'\n';
srand((unsigned)time(NULL));
// pay (a,b]
int a=0,b=100;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
out<< (rand() % (b-a))+ a + 1<<" ";
}
out<<endl;
}
out.close();
六、测试结果
时间复杂度: O(n!)
对于工作分配问题,如果有 n 个工人和 n 个任务,一种简单的回溯算法的时间复杂度是 O(n!)。其解空间是一个排列组合问题。对于每个工人,都有 n 种可能的任务分配方案。因此,总的搜索空间是 n 的阶乘 (n!)。
在回溯算法中,通常会使用剪枝技术来减少搜索空间,提高算法效率。但在最坏情况下,当没有剪枝的情况下,时间复杂度仍然是 O(n!)。这是因为在每一步,都要考虑所有可能的分支。
自定义输入n的数值的测试结果:
七、实验心得
通过这次实验,我了解熟悉了回溯算法实现工作分配问题的求解过程及原理。对于自己实现的案例,感觉存在误差,画出来的图不符合直觉,也可能是求解运行时间的程序有问题。
八、完整代码
#include <iostream>
#include <fstream>
#include <windows.h>
#include <time.h>
#include <cmath>
#include <vector>
using namespace std;
// 定义一个宏,用于获取三个数的最小值
#define mins(a,b,c) min(min(a,b),c)
int dpComp(int k,string A,string B)
{
int len1=A.length(),len2=B.length();
vector<vector<int> >dp(len1+1,vector<int>(len2+1));
for(int i=1;i<=len1;i++)//预处理
{
dp[i][0]=k*i;
}
for(int i=1;i<=len2;i++)//预处理
{
dp[0][i]=k*i;
}
dp[0][0]=0;
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
dp[i][j]=mins(dp[i-1][j]+k,dp[i][j-1]+k,dp[i-1][j-1]+abs(A[i-1]-B[j-1]));
}
}
return dp[len1][len2];
}
int main(int argc, char** argv) {
ofstream out1("output.txt");
int x=1;
while(1){
//生成规模为n的随机数
cout<<"请输入数据规模n:"<<endl;
int n,c;
cin>>n;
ofstream out("input1.txt");
srand((unsigned)time(NULL));
/*
ASCII码的可见字符包括数字、字母、标点符号和一些特殊字符。
其中,0-31是控制字符,32是空格,33-126是可打印字符,127是DEL(删除)字符。
*/
//字符串A长度为n,都为可打印字符
for(int i=0;i<n;i++){
out<<static_cast<char>(' ' + std::rand() % (126 - ' ' + 1));
}
out<<endl;
//[a,b]
int a=0,b=n;
int Bl=(rand() % (b-a))+ a + 1;
//字符串B长度为Bl,范围是[a,b],都为可打印字符
for(int i=0;i<Bl;i++){
out<<static_cast<char>(' ' + std::rand() % (126 - ' ' + 1));
}
out<<endl;
//k,不妨设k的值在0-100之间
a=0,b=100;
out<<(rand() % (b-a))+ a + 1<<"\n";
out.close();
int i,maxi,mini;
LARGE_INTEGER nFreq,nBegin,nEnd;
double time;
ifstream in("input1.txt");
string A,B;
//in>>A;
getline(in,A);
n=A.length();
//in>>B;
getline(in,B);
Bl=B.length();
if(n<1000){
cout<<"字符串A: "<<n<<"\t"<<A<<endl;
cout<<"字符串B: "<<Bl<<"\t"<<B<<endl;
}
else{
cout<<"字符串A: "<<n<<endl;
cout<<"字符串B: "<<Bl<<endl;
}
int k;
in>>k;
cout<<"空格与其它字符的距离k: "<<k<<endl;
long long result;
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nBegin);
result=dpComp(k,A,B);
QueryPerformanceCounter(&nEnd);
time=(double)(nEnd.QuadPart-nBegin.QuadPart)/(double)nFreq.QuadPart;
cout<<"结果:"<<result<<"\n查询时间:"<<time<<endl<<endl;
out1<<n<<' '<<time<<endl;
in.close();
}
out1.close();
return 0;
}
九、绘图代码
import matplotlib.pyplot as plt
# 读取txt文件,假设文件名为data.txt
file_path = 'F:\\3-CourseMaterials\\3-1\\3-算法设计与分析\实验\lab3\\1-code\\3-字符串比较问题\\output.txt'
# 存储x和y的列表
x_values = []
y_values = []
# 读取文件并提取数据
with open(file_path, 'r') as file:
for line in file:
# 假设数据以空格或逗号分隔
x, y = map(float, line.strip().split())
x_values.append(x)
y_values.append(y)
print(x_values)
print(y_values)
# 绘制图形
plt.plot(x_values, y_values)
plt.title('X vs Y Plot')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.grid(True)
plt.show()