一、题目
- 假设有n个任务需要分配给n个人执行,每个人只执行一个任务,每个任务只由一个人执行。第i个人执行第j个任务的成本是Cij(1<=i,j<=n), 求解初最小成本的分配方案。
二、代码
分支限界法,采用链表孩子树和优先队列。
#include<iostream>
using namespace std;
const int msize = 50;
int cost[msize][msize];
bool visited[msize] = { 0 };
int exp_mincost[msize];
struct node
{//树节点
node(int p,int m)
{
pno = p;
mno = m;
ncost = 0;
father = next = childs = NULL;
}
int mno;
int pno;
int ncost;
int expcost;
node* next;
node* childs;
node* father;//找爹
};
node* pri_list[msize];//优先队列
int n, front = 0,last = 0;//物品个数,背包容量,循环队列的两个指针
int c_max, c_min;//上下界
bool solveNode(node*& pnow)
{//处理pnow节点
pnow->ncost = pnow->expcost = cost[pnow->pno][pnow->mno] + pnow->father->ncost;
for (int i = pnow->pno+1; i < n; i++)
{
pnow->expcost += exp_mincost[i];
}
if (pnow->expcost >= c_min && pnow->expcost <= c_max)
{//该点可以加入队列
bool acc_que = 0;//已经进队列了没
for (int i = front; i != last; i = (i + 1) % msize)
{//循环++
if (pri_list[i]->expcost > pnow->expcost)
{//找到位置,从小到大优先
acc_que = 1;
for (int j = last; j != i; j = (j - 1) < 0 ? msize - 1 : j - 1)
{//循环++,移位
int j_prev = j - 1 < 0 ? msize - 1 : j - 1;
pri_list[j] = pri_list[j_prev];
}
pri_list[i] = pnow;//放进去
break;
}
}
if (!acc_que)
{//没加入,直接放最后
pri_list[last] = pnow;
}
last = (last + 1) % msize;//本次插入一个数据
}
else
{//越界,剪枝
cout << "-丢弃,exp_cost = " << pnow->expcost<<endl;
return 0;
}
return 1;
}
void delTree(node* root)
{
if (root->childs)
{
delTree(root->childs);
}
if (root->next)
{
delTree(root->next);
}
delete root;
}
int greed()
{
bool avisited[msize] = { 0 };//任务做过没
int total_cost = 0;
for (int i = 0; i < n; i++)
{//每一个人
int mincost = cost[i][0];
int last = -1;
for (int j = 0; j < n; j++)
{
if (cost[i][j] < mincost && !avisited[j])
{
last = j;
mincost = cost[i][j];
}
}
if(last != -1)avisited[last] = 1;
total_cost += mincost;
}
return total_cost;
}
int main()
{
cin >> n;
if (n < 1)
{
cout << 0;
return 0;
}
int tcost;
for (int i = 0; i < n; i++)
{//输入数据
for (int j = 0; j < n; j++)
{
cin >> tcost;
if (tcost >= 0)
{
cost[i][j] = tcost;
}
}
}
c_max = greed();
c_min = 0;
for (int i = 0; i < n; i++)
{
int mincost = cost[i][0];
for (int j = 0; j < n; j++)
{
if (cost[i][j] < mincost)
{
mincost = cost[i][j];
}
}
c_min += mincost;
exp_mincost[i] = mincost;
}
cout << "目标区间为 [" << c_min << ", " << c_max << "]" << endl;
node* root = new node(-1, -1);
root->ncost = 0;
root->expcost = 0;
pri_list[last++] = root;
node* pnow = NULL,*plast = NULL;
while (pri_list[front]->pno < n - 1)
{
pnow = pri_list[front];
memset(visited, 0, sizeof(bool) * msize);
node* find = pnow;
while (find != root)
{//多个支线可能导致占用已访问标志
visited[find->mno] = 1;
find = find->father;
}
front = (front + 1) % msize;
for (int i = 0; i < n; i++)
{
if (!visited[i])
{
visited[i] = 1;
if (pnow->childs == NULL)
{//第一个孩子
pnow->childs = new node(pnow->pno + 1, i);
pnow->childs->father = pnow;
if (!solveNode(pnow->childs))
{
visited[i] = 0;
delete pnow->childs;
pnow->childs = NULL;
}
}
else
{
node* pnew = pnow->childs;
while (pnew->next)
{
pnew = pnew->next;
}
pnew->next = new node(pnow->pno + 1, i);
pnew->next->father = pnow;
if (!solveNode(pnew->next))
{
visited[i] = 0;
delete pnew->next;
pnew->next = NULL;
}
}
}
}
}
plast = pri_list[front];//这存的就是最后结果
cout << endl << endl << "分支界限法结果为: " << plast->ncost << endl;
int count = 0;
int stackk[msize];//省的反向输出
while (plast != root)
{//回溯,root存的-1,没用
stackk[count++] = plast->mno+1;
plast = plast->father;
}
int cmax = count;//懒得写,cmax-count是第几个
cout << endl <<n<< "人分别选取了第";
while (count > 0)
{
cout << stackk[--count] << ", ";
if ((cmax-count) % 10 == 0)
{
cout <<"个和"<< endl<<"第";
}
}
cout << "个任务" << endl;
delTree(root);//后序递归删树
return 0;
}
/*
4
9 2 7 8 6 4 3 7
5 8 1 8 7 6 9 4
*/
三、运行结果
四、心得体会
写了一晚上,累死我了。