和尚挑水安排(回溯问题)

来源:互联网 发布:linux终端进度条 编辑:程序博客网 时间:2024/05/16 06:38

题意:总共7个和尚,编号从1到7,星期的编号也是1到7,在一个星期中每个和尚在固定的某一天或几天挑水,求所有安排方案,使得每个和尚在一个星期中都有且仅有在其中某一天挑水。


举例: 和尚1 在星期2和星期4挑水

和尚2在星期1和星期6挑水

和尚3在星期3和星期7挑水

和尚4在星期5挑水

和尚5在星期1、星期4和星期6挑水

和尚6在星期2和星期5挑水

和尚7在星期3、星期6和星期7挑水


所有的安排方案有四种:

和尚2在星期1挑水,和尚6在星期2挑水,和尚3在星期3挑水,和尚1在星期4挑水,和尚4在星期5挑水,和尚5在星期6挑水,和尚7在星期7挑水

和尚2在星期1挑水,和尚6在星期2挑水,和尚7在星期3挑水,和尚1在星期4挑水,和尚4在星期5挑水,和尚5在星期6挑水,和尚3在星期7挑水

和尚5在星期1挑水,和尚6在星期2挑水,和尚3在星期3挑水,和尚1在星期4挑水,和尚4在星期5挑水,和尚2在星期6挑水,和尚7在星期7挑水

和尚5在星期1挑水,和尚6在星期2挑水,和尚7在星期3挑水,和尚1在星期4挑水,和尚4在星期5挑水,和尚2在星期6挑水,和尚3在星期7挑水


请编程实现,输入为7行,每行用7个数字0或者1表示,每个数字之间用空格隔开,0表示该和尚当天不跳水,1表示该和尚当天挑水。输出首先第一行是方案数,后面每一行输出一种方案,每个方案用7个和尚编号表示,每个数字用空格隔开,方案之间按照和尚编号的大小进行排序。


输入样例: 

0 1 0 1 0 0 0

1 0 0 0 0 1 0

0 0 1 0 0 0 1

0 0 0 0 1 0 0

1 0 0 1 0 1 0

0 1 0 0 1 0 0

0 0 1 0 0 1 1


输出样例:

4

2 6 3 1 4 5 7

2 6 7 1 4 5 3

5 6 3 1 4 2 7

5 6 7 1 4 2 3


分析:从题目说来看第一感觉有点难,看着比较乱,不知道怎么下手,再看下给出来的输入,是一个二维矩阵,可以联想到剑指offer上涉及到二维矩阵的那道“矩阵中的路径”题,这是一个经典的回溯类型的题。我们从每一天安排合适的和尚挑水入手,比如星期1安排和尚2挑水,接下来安排星期二,如果能安排,就继续往下安排,等安排满七天之后就记录该方案。如果安排不下去,则回溯到上一天,在上一天中寻找除了刚才那个和尚之外的合适的和尚。重复安排过程步骤。

回溯问题最关键的一点是找到可以作为入栈的关键元素,比如这题入栈元素我选择了和尚编号,“矩阵中的路径”那道题我选择了坐标。然后写一个while循环判空,根据入栈元素的栈展开下一次搜索。


以下是我的代码:

#include <iostream>
#include <vector>
#include <stack>
#include <set>


using namespace std;




int main()
{
//定义输入(测试用例),行代表和尚从1到7的编号,列代表星期1到7,数值1表示当天该和尚挑水
int array[7][7] = 
{ {0, 1, 0, 1, 0, 0, 0},
{1, 0, 0, 0, 0, 1, 0},
{0, 0, 1, 0, 0, 0, 1},
{0, 0, 0, 0, 1, 0, 0},
{1, 0, 0, 1, 0, 1, 0},
{0, 1, 0, 0, 1, 0, 0},
{0, 0, 1, 0, 0, 1, 1}
};


int test = 0;//用测试用例的时候把test设为0,不用麻烦自己输入,需要自己测试其他情况时把test改成1
if (1 == test)
{
//得到输入
for (int i = 0; i < 7; ++i)
{
for (int j = 0; j < 7; ++j)
{
cin >> array[i][j];
}
}
}



//利用回溯法来解题,回溯法的核心是某个元素的出栈入栈
//确定周一挑水的和尚编号,有多少个和尚就循环多少次,共七个和尚,所以循环7次
vector<vector<int> > res;
for (int i = 0; i < 7; ++i)
{
//和尚编号入栈后利用回溯法确定其他时间挑水的和尚编号
if (1 == array[i][0])
{
//定义和尚栈容器,按星期顺序记录可以安排任务的和尚编号
stack<int> monks;
monks.push(i + 1);


//定义星期栈容器,记录已经安排好和尚的星期编号
stack<int> weekTime;
weekTime.push(1);


//定义已经有任务的和尚编号容器,记录已经有任务的和尚编号
set<int> monkHasTask;
monkHasTask.insert(i + 1);


//定义临时结果,按星期顺序记录满足安排条件的和尚编号
vector<int> vecTemp;
vecTemp.push_back(i + 1);
int cnt = 1;//定义已安排和尚的数量计数


int lastStart = 0;//定义上一次退栈时该天的和尚编号


//回溯,只要和尚人数不为空,就一直执行,也就是说只要不回溯到起点就一直寻找下去
while (!monks.empty())
{
//得到当前最近安排的和尚编号以及星期编号
int monk = monks.top();
int weekday = weekTime.top();


//如果当前已经满足安排方案,则记录,并让相关的容器出栈以便开始下一次搜索
if (7 == cnt)
{
//记录方案
res.push_back(vecTemp);


//将当前最近安排的和尚编号以及星期编号出栈
monks.pop();
weekTime.pop();
vecTemp.pop_back();


//删除当前最近安排的和尚编号,记录该编号作为下次开始安排搜索的编号起点
monkHasTask.erase(monkHasTask.find(monk));
cnt--;//计数减1
lastStart = monk;


continue;
}


//找出下一天可以安排任务的和尚编号
bool flag = false;
int nextWeekday = weekday + 1;//时刻注意数字和数组下标是相差1的
for (int k = lastStart; k < 7; ++k)
{
//如果该和尚当天可以挑水,并且之前没有安排过任务,则可以安排
if ((1 == array[k][nextWeekday - 1]) &&(monkHasTask.find(k + 1) == monkHasTask.end()))
{
monks.push(k + 1);//记录该和尚编号
weekTime.push(nextWeekday);//记录该天编号
vecTemp.push_back(k + 1);//记录该和尚编号
cnt++;//计数加1
monkHasTask.insert(k + 1);//记录该和尚编号
flag = true;
lastStart = 0;//重置开始下标


break;
}
}


//如果找到可以安排任务的和尚编号,则跳过后面步骤,开始下次安排
if (flag)
{
continue;
}
//否则让相关的容器出栈以便开始下一次搜索
else
{
//将当前最近安排的和尚编号以及星期编号出栈
monks.pop();
weekTime.pop();
vecTemp.pop_back();


//删除当前最近安排的和尚编号,记录该编号作为下次开始安排搜索的编号起点
monkHasTask.erase(monkHasTask.find(monk));
cnt--;//计数减1
lastStart = monk;
}


}//end while


}//end if
}//end for


//输出
int resSize = res.size();
cout << resSize << endl;//输出方案数
for (int i = 0; i < resSize; ++i)
{
for (int j = 0; j < 6; ++j)
{
cout << res[i][j] << ' ';
}
cout << res[i][6] << endl;
}


return 0;
}


0 0
原创粉丝点击