某寺庙里7个和尚:轮流挑水,为了和其他任务不能冲突,各人将有空天数列出如下表: 和尚1: 星期二,四; 和尚2: 星期一,六; 和尚3: 星期三,日; 和尚4: 星期五; 和尚5: 星期一,四,六; 和尚6: 星期二,五; 和尚7: 星期三,六,日; 请将所有合理的挑水时间安排表 。
输入为每个和尚从周一到周日的空闲情况,输出则为所有可能的任务安排表。
这道题用到了回溯法(backtracking),它的基本思想如下:
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
在本题中,每一种挑水策略为一个解,可从星期一开始,先选择一个空闲的和尚(如和尚1),接着到星期二再选一个和尚(空闲且非和尚1),接着依次类推直到星期日选完和尚,如果中途某天没有满足要求的和尚,则放弃该解。在每天选和尚时,都有(该天空闲和尚数-已挑过水的和尚数)种选择,通过穷举的方法,可以不断将解的范围扩大,直到遍历所有解。
下面给出具体代码:
#include <iostream>
using namespace std;
struct st
{
int isFree[7]; //和尚某天是否空闲(1为空闲)
bool haveWorked; //在一次解法中是否挑过水(1为挑过水)
}monk[7];
int sum; //方案总数
int x[7]={0}; //具体方案数组,1表示挑水,0表示不挑水
void backtrack(int n)
{
int i=0;
if(n==7) //得到一组解并输出
{
sum++;
cout<<"第"<<sum<<"种方案:"<<endl;
for(i=0;i<7;i++)
cout<<x[i]<<' ';
cout<<endl;
}
else
{
for(i=1;i<=7;++i)
{
if(monk[i-1].isFree[n]==1&&monk[i-1].haveWorked==false) //和尚i没有挑过水且在星期n有时间
{
x[n]=i;
monk[i-1].haveWorked=true;
backtrack(n+1);
monk[i-1].haveWorked=false;
}
}
}
}
int main()
{
int b[7][7]={{1,0,0,1,0,1,0},{0,0,1,1,0,0,0},{0,1,0,0,1,0,1},{1,1,0,0,0,1,0},\
{0,0,1,0,1,0,0},{0,0,0,1,0,0,1},{1,0,0,0,1,0,0}};
int i,j;
for(i=0;i<7;++i) //状态初始化
{
for(j=0;j<7;++j)
monk[i].isFree[j]=b[i][j];
monk[i].haveWorked=false;
}
backtrack(0);
return 0;
}