引例
任务分配问题要求把n项任务分配给n个人,
每个人完成每项任务的成本不同,要求分配总成本
最小的最优分配方案。如图所示是一个任务分
配的成本矩阵。
由于本题是一个最优化问题,考虑设计搜索树进行最优搜索求解。
使用算法设计中一个常见的回溯法技巧,分支限界法
既然考虑使用分支限界法,在存放搜索树结点和取用搜索树结点时,考虑设计堆来进行管理
考虑任意一个可行解,例如矩阵中的对角线是一个合法的选择,
表示将任务1分配给人员a、任务2分配给人员b、任务3分配给
人员c、任务4分配给人员d,其成本是9+4+1+4=18;或者应用
贪心法求得一个近似解:将任务2分配给人员a、任务3分配给
人员b、任务1分配给人员c、任务4分配给人员d,其成本是
2+3+5+4=14。显然,14是一个更好的上界。
为了获得下界,考虑人员a执行所有任务的最小代价是2,人员
b执行所有任务的最小代价是3,人员c执行所有任务的最小代
价是1,人员d执行所有任务的最小代价是4。因此,将每一行
的最小元素加起来就得到解的下界,其成本是2+3+1+4=10。
需要强调的是,这个解并不是一个合法的选择(3和1来自于矩
阵的同一列),它仅仅给出了一个参考下界,这样,最优值一
定是[10, 14]之间的某个值。
设当前已对人员1~i分配了任务,并且获得了成本v
,则限界函数可以定义为:
应用分支限界法求解图1所示任务分配问题,对
解空间树的搜索如下图所示
针对本题的静态代码设计
//在这里插入代码片
/*
完成任务分配分支限界法的非递归代码
->需要压堆,出堆动作
->需要输出最终结果
*/
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define N 4
#define INF 0x3f3f3f3f
typedef struct Lnode
{
int elevate;
int task;
struct Lnode* parent;
}TreeNode;
void Init_root(TreeNode*&root,int min)//传入一个TreeNode指针的引用,对这个指针初始化
{
root=(TreeNode*)malloc(sizeof(TreeNode));
root->elevate=min;
root->parent=NULL;
root->task=-1;
}
void sift_up(TreeNode**&heap,int i)//指针数组的引用
{
int done=0;
int w=i;
if(i==1)
return;
while(i>1&&done==0)
{
{
if(heap[i]->elevate<heap[i/2]->elevate)
{
TreeNode*temp=heap[i];
heap[i]=heap[i/2];
heap[i/2]=temp;
}
else
{
done=1;
}
i=i/2;
}
}
}
void sift_down(TreeNode**&heap,int i,int n)
{
int done=0;
if(2*i>n)
{
return;
}
while(2*i<=n&&done==0)
{
i=2*i;
if(i+1<=n&&heap[i+1]->elevate < heap[i]->elevate)
{
i=i+1;
}
if(heap[i/2]->elevate > heap[i]->elevate)
{
TreeNode*temp=heap[i/2];
heap[i/2]=heap[i];
heap[i]=temp;
}
else
{
done=1;
}
}
}
int Insert(TreeNode **&heap,int n,TreeNode* x)//heap:(0),1,2,....n
{
n=n+1;
heap = (TreeNode**)realloc(heap, (n+1) * sizeof(TreeNode*));
heap[n]=x;
sift_up(heap,n);
return n;
}
int Delete(TreeNode **&heap,int i,int n)//1 =< i <= n
{
TreeNode*x=heap[i];
TreeNode*y=heap[n];
n=n-1;
//printf("n=%d\n",n);
if(i==n+1)
return n;
heap[i]=y;
if(y->elevate<=x->elevate)
{
sift_up(heap,i);
}
else
{
sift_down(heap,i,n);
}
return n;
}
TreeNode* DeleteMax(TreeNode**&heap,int *n)
{
TreeNode*x=heap[1];
*n=Delete(heap,1,*n);
return x;
}
int main()
{
int C[N][N]={
{9,2,7,8},//人员a
{6,4,3,7},//人员b
{5,8,1,8},//人员c
{7,6,9,4},//人员d
};
int task[N]={0,0,0,0};
int max=0;
int min=0;
int min_for_each[N]={0,0,0,0};
for(int i=0;i<N;i++)
{
int min_temp=INF;
int task_temp=-1;
for(int j=0;j<4;j++)
{
if(task[j]==0)
{
if(min_temp>C[i][j])
{
min_temp=C[i][j];
task_temp=j;
}
}
}
max+=min_temp;
task[task_temp]=1;
}
for(int i=0;i<N;i++)
{
int min_temp=INF;
for(int j=0;j<N;j++)
{
if(min_temp>C[i][j])
{
min_temp=C[i][j];
}
}
min_for_each[i]=min_temp;
min+=min_temp;
}
TreeNode*root;
Init_root(root,min);
TreeNode **heap= (TreeNode**)malloc(2*sizeof(TreeNode*));
heap[0]=NULL;
heap[1]=root;
int heap_size=1;
int id=1;//没有找到最优
while(id==1)
{
TreeNode* out_heap=DeleteMax(heap,&heap_size);
int current_able_task[N]={0,0,0,0};//0 means this task hasn't been arranged
TreeNode*temp_track=out_heap;
int w=0;
while(temp_track->parent!=NULL)
{
current_able_task[temp_track->task]=1;
temp_track=temp_track->parent;
w++;
}//检测出堆的结点到根的路径中所有已经被安排的任务,置1
if(w==4)//到达叶子结点层(实现一种任务分配)
{
for(int i=1;i<=heap_size;i++)
{
if(heap[i]->elevate<out_heap->elevate)
{
id=0;
break;
}
}
if(id==1)
{
TreeNode*temp=out_heap;
int output[N];
int j=3;
while(temp->parent!=NULL)
{
output[j]=temp->task;
j--;
temp=temp->parent;
}
printf("任务分配方案如下:\n");
for(int j=0;j<N;j++)
{
printf("%d ",output[j]+1);
}
printf("\n");
break;
}
}
for(int i=0;i<N;i++)//四种任务
{
int elev=out_heap->elevate-min_for_each[w]+C[w][i];//更新安排这个人后的估计值
if(elev>=min&&elev<=max&¤t_able_task[i]==0)//任务合法
{
TreeNode*New_node;
Init_root(New_node,elev);
New_node->task=i;
New_node->parent=out_heap;
heap_size=Insert(heap,heap_size,New_node);//压堆
}
}
}
system("PAUSE");
}