高一的时候,同学(老友)问过这个题,我现在还记得他当时的话语:“这道题,我们那边的一个大学生想了三天都没想出来,呵呵,让我解决了,厉害吧,哈哈。。。”。当时,我也是冲着他那句“大学生三天都没想出来”,才兴致勃勃地答应挑战的,可惜,当时终究是没想出来,最后,只好逼着他讲答案。当然,经历过大学的人都知道,其实大学生也就那样,大多很普通,无须过高的评价。
题意大致如下:
有6个怪物AaBbCc,ABC是大怪物,abc是小怪物,Aa、Bb、Cc是亲子关系,当小怪物的父亲不在身边时,别的大怪物会吃这个小怪物,大怪物不会吃自己的孩子,现在这6个怪物要过河,这边有一条船,船最多可载2只大怪物,或3只小怪物,当然也可以载1只大怪物和1只小怪物,其中知道ABCa会划船,bc不会,想办法让6个怪物都安全到对岸。
大学后,我开始尝试用编程来解决它,但每每不能成功,最大的难点是,我不知如何把实际问题抽象出来。这样,每过一年,我都会再尝试一次。
“辛波那契数列”也是我大学时遇到的一个难题,不过,在去年的时候已经解决了,但在我的博客中居然没有相关信息,所以上篇文章再写了这个问题。
想到“怪物过河问题”是我之前想解决而又未解决的最后一个问题,昨天晚上又开始了新的尝试,还好这次编码非常顺利,终于,抽象出问题来了,中间的一些小的编码漏洞,和个别特殊情况引发的思维漏洞,让我找了一个通宵,早上睡了4个小时,出去购物回来已是中午2点,继续找,终于全部把漏洞全补好了。
我的实现如下:
#include <list>
#include <string>
#include <iostream>
using namespace std;
struct Monster
{
char place;
const char name;
const bool paddle;
const short weight;
};
Monster monster[6] =
{
{ 'H', 'A', true, 3 },
{ 'H', 'B', true, 3 },
{ 'H', 'C', true, 3 },
{ 'H', 'a', true, 2 },
{ 'H', 'b', false, 2 },
{ 'H', 'c', false, 2 }
};
int left_count = 6; /* 剩余需要过河的怪物 */
const int boat_load_weight = 6; /* 船的最大载重量 */
list<int> move_index_list; /* 记录每次过河的怪物 */
static bool check(int, char);
static bool move(char, char);
static bool try_move(int, char, char);
static void change_place(int, char);
static void display_way();
void display_state(bool go);
/*
* 检测此次过河方案是否安全
*/
bool check(int current_move_index, char to)
{
/*
* 不允许出现相同的怪物做往返过河的动作
* 比如: Xx(H->T), Xx(H<-T)
* 下面特例也需视为此情形:
* 1. Yy(->), Zz(<-))
* 2. Zz(->), Yy(<-))
*/
if (!move_index_list.empty())
{
int last_move_index = move_index_list.back();
if (last_move_index == current_move_index ||
(25 == last_move_index &&
36 == current_move_index) ||
(36 == last_move_index &&
25 == current_move_index))
{
return(false);
}
}
/* 检查各小怪物 (xyz) 是否安全 */
for (int i = 3; i < 6; ++i)
{
const Monster & young = monster[i];
const Monster & old = monster[i - 3];
if (young.place == old.place)
{
continue; /* 此时 young 必定安全 */
}
for (int j = 0; j < 3; ++j)
{
if (i - 3 == j)
{
continue;
}
/*
* 1.检查船在河中时, young 是否有安全
* 2.检查船到彼岸后, young 是否有安全
*/
const Monster & enemy = monster[j];
if (young.place == enemy.place ||
(to == young.place &&
'B' == enemy.place &&
'B' != old.place) ||
('B' == young.place &&
to == enemy.place &&
to != old.place))
{
return(false);
}
}
}
if ('T' == to) /* 'T' 代表 There */
{
return(true);
}
/*
* (否则)如果船是返回方向的, 此时
* 不能变成最初的状态, 即:
* 不能出现所有怪物都回来的现象
* 比如:
* xyz(H->T), xy(H<-T), x(H->T), xy(H<-T)
* 且不论上面四步是在做无用功,
* 最可怕的是会使搜索陷入无限递归中
*/
for (int j = 0; j < 6; ++j)
{
const Monster & checker = monster[j];
if ('T' == checker.place)
{
return(true);
}
}
return(false);
}
bool move(char from, char to)
{
for (int index = 0; index < 6; ++index)
{
if (try_move(index, from, to))
{
return(true);
}
}
return(false);
}
bool try_move(int boatman_index, char from, char to)
{
Monster & boatman = monster[boatman_index]; /* 划船者 */
if (!boatman.paddle || boatman.place != from)
{
return(false);
}
/* 此次上船的怪物数 */
int current_move_count = 0;
/* 记录此次上船的怪物, 比如14代表Xx, 456代表xyz */
int current_move_index = 0;
/* 上船的怪物的总重量 */
int current_total_weight = 0;
/*
* monster[index] 是划船者, 它后面的可以搭载
* 开始搜索可行的上船方案
*/
int index = boatman_index;
while (index < 6)
{
Monster & checker = monster[index];
if (checker.place != from)
{
++index;
continue;
}
/* checker 可以上船 */
if (boat_load_weight >=
current_total_weight + checker.weight)
{
current_move_count += 1;
current_move_index *= 10;
current_move_index += index + 1;
current_total_weight += checker.weight;
checker.place = 'B'; /* 'B' 代表 Boat */
if (check(current_move_index, to))
{
change_place(current_move_index, to);
move_index_list.push_back(current_move_index);
if ('H' == from) /* 'H' 代表 Here */
{
left_count -= current_move_count;
}
else
{
left_count += current_move_count;
}
if (left_count <= 0) /* 全部到达了目的地 */
{
return(true);
}
else if (move(to, from)) /* 尝试把船划回来 */
{
return(true);
}
else /* 方案不可行, 回到之前的状态 */
{
change_place(current_move_index, 'B');
move_index_list.pop_back();
if ('H' == from)
{
left_count += current_move_count;
}
else
{
left_count -= current_move_count;
}
}
}
}
else /* 让前一个下船, 换一个尝试 */
{
index = current_move_index % 10 - 1;
Monster & old_checker = monster[index];
old_checker.place = from;
current_total_weight -= old_checker.weight;
current_move_count -= 1;
current_move_index /= 10;
}
++index;
}
change_place(current_move_index, from);
return(false);
}
void change_place(int current_move_index, char to)
{
while (current_move_index > 0)
{
int index = current_move_index % 10 - 1;
Monster & mover = monster[index];
mover.place = to;
current_move_index /= 10;
}
}
/*
* 显示路线
*/
void display_way()
{
for (int index = 0; index < 6; ++index)
{
Monster & mst = monster[index];
mst.place = 'H';
}
const char place[3] = { 'H', 'B', 'T' };
int move_base = 1;
bool go = true;
list<int>::iterator iter = move_index_list.begin();
while (move_index_list.end() != iter)
{
change_place(*iter, place[1]);
display_state(go);
change_place(*iter, place[1 + move_base]);
move_base *= -1;
go = !go;
++iter;
}
}
/*
* 显示当前状态
*/
void display_state(bool go)
{
string here;
here.reserve(16);
string boat;
boat.reserve(16);
boat = "\\_";
string there;
there.reserve(16);
there = "|";
for (int index = 0; index < 6; ++index)
{
const Monster & checker = monster[index];
switch (checker.place)
{
case 'H':
{
here += checker.name;
break;
}
case 'B':
{
boat += checker.name;
break;
}
case 'T':
{
there += checker.name;
break;
}
default:
{
break;
}
}
}
here += "|";
boat += "_/";
int left_distance = (go ? 0 : 8);
string left_water(left_distance, '_');
string right_water(8 - left_distance, '_');
cout << here << left_water
<< boat << right_water
<< there << endl;
}
int main(int argc, char * argv[])
{
if (move('H', 'T'))
{
display_way();
cout << " move times: "
<< move_index_list.size() << endl;
}
else
{
cout << "there is no solution "
"for this question." << endl;
}
return(0);
}
当前代码运行结果如下:
ACac|\_Bb_/________|
ACac|________\_B_/|b
ABC|\_ac_/________|b
ABC|________\_a_/|bc
Aa|\_BC_/________|bc
Aa|________\_Bb_/|Cc
Bb|\_Aa_/________|Cc
Bb|________\_Cc_/|Aa
bc|\_BC_/________|Aa
bc|________\_a_/|ABC
c|\_ab_/________|ABC
c|________\_C_/|ABab
|\_Cc_/________|ABab
move times: 13
上面的代码只是求问题的第一个可行解,这个解决方案可能不是最优的。
把move()中的for逆序写,可得到结果如下:
ABCc|\_ab_/________|
ABCc|________\_a_/|b
ABC|\_ac_/________|b
ABC|________\_a_/|bc
Aa|\_BC_/________|bc
Aa|________\_Cc_/|Bb
Cc|\_Aa_/________|Bb
Cc|________\_Bb_/|Aa
bc|\_BC_/________|Aa
bc|________\_a_/|ABC
c|\_ab_/________|ABC
c|________\_a_/|ABCb
|\_ac_/________|ABCb
move times: 13
从这里可以很清楚地看出这不是最优解。
修改代码,求最优解:
#include <list>
#include <string>
#include <iostream>
using namespace std;
struct Monster
{
char place;
const char name;
const bool paddle;
const short weight;
};
Monster monster[6] =
{
{ 'H', 'A', true, 3 },
{ 'H', 'B', true, 3 },
{ 'H', 'C', true, 3 },
{ 'H', 'a', true, 2 },
{ 'H', 'b', false, 2 },
{ 'H', 'c', false, 2 }
};
int left_count = 6; /* 剩余需要过河的怪物 */
const int boat_load_weight = 6; /* 船的最大载重量 */
list<int> move_index_list; /* 记录怪物过河的方案 */
list<int> best_move_index_list; /* 记录最优方案 */
static bool check(int, char);
static void move(char, char);
static void try_move(int, char, char);
static void change_place(int, char);
static void display_best_way();
void display_state(bool go);
/*
* 检测此次过河方案是否安全
*/
bool check(int current_move_index, char to)
{
/*
* 不允许出现相同的怪物做往返过河的动作
* 比如: Xx(H->T), Xx(H<-T)
* 下面特例也需视为此情形:
* 1. Yy(->), Zz(<-))
* 2. Zz(->), Yy(<-))
*/
if (!move_index_list.empty())
{
int last_move_index = move_index_list.back();
if (last_move_index == current_move_index ||
(25 == last_move_index &&
36 == current_move_index) ||
(36 == last_move_index &&
25 == current_move_index))
{
return(false);
}
}
/* 检查各小怪物 (xyz) 是否安全 */
for (int i = 3; i < 6; ++i)
{
const Monster & young = monster[i];
const Monster & old = monster[i - 3];
if (young.place == old.place)
{
continue; /* 此时 young 必定安全 */
}
for (int j = 0; j < 3; ++j)
{
if (i - 3 == j)
{
continue;
}
/*
* 1.检查船在河中时, young 是否有安全
* 2.检查船到彼岸后, young 是否有安全
*/
const Monster & enemy = monster[j];
if (young.place == enemy.place ||
(to == young.place &&
'B' == enemy.place &&
'B' != old.place) ||
('B' == young.place &&
to == enemy.place &&
to != old.place))
{
return(false);
}
}
}
if ('T' == to) /* 'T' 代表 There */
{
return(true);
}
/*
* (否则)如果船是返回方向的, 此时
* 不能变成最初的状态, 即:
* 不能出现所有怪物都回来的现象
* 比如:
* xyz(H->T), xy(H<-T), x(H->T), xy(H<-T)
* 且不论上面四步是在做无用功,
* 最可怕的是会使搜索陷入无限递归中
*/
for (int j = 0; j < 6; ++j)
{
const Monster & checker = monster[j];
if ('T' == checker.place)
{
return(true);
}
}
return(false);
}
void move(char from, char to)
{
for (int index = 0; index < 6; ++index)
{
try_move(index, from, to);
}
}
void try_move(int boatman_index, char from, char to)
{
/* 如果当前方案不可能是最优方案, 则无需继续尝试 */
if (!best_move_index_list.empty() &&
best_move_index_list.size() <=
move_index_list.size() + 1)
{
return;
}
Monster & boatman = monster[boatman_index]; /* 划船者 */
if (!boatman.paddle || boatman.place != from)
{
return;
}
/* 此次上船的怪物数 */
int current_move_count = 0;
/* 记录此次上船的怪物, 比如14代表Xx, 456代表xyz */
int current_move_index = 0;
/* 上船的怪物的总重量 */
int current_total_weight = 0;
/*
* monster[index] 是划船者, 它后面的可以搭载
* 开始搜索可行的上船方案
*/
int index = boatman_index;
while (index < 6)
{
Monster & checker = monster[index];
if (checker.place != from)
{
++index;
continue;
}
/* checker 可以上船 */
if (boat_load_weight >=
current_total_weight + checker.weight)
{
current_move_count += 1;
current_move_index *= 10;
current_move_index += index + 1;
current_total_weight += checker.weight;
checker.place = 'B'; /* 'B' 代表 Boat */
if (check(current_move_index, to))
{
/* 修改状态 */
change_place(current_move_index, to);
move_index_list.push_back(current_move_index);
if ('H' == from) /* 'H' 代表 Here */
{
left_count -= current_move_count;
}
else
{
left_count += current_move_count;
}
if (left_count <= 0) /* 全部到达了目的地 */
{
if (best_move_index_list.empty() ||
best_move_index_list.size() >
move_index_list.size())
{
best_move_index_list.assign
(
move_index_list.begin(),
move_index_list.end()
);
}
}
else /* 继续尝试 */
{
move(to, from); /* 把船划回来 */
}
/* 改回到之前的状态 */
change_place(current_move_index, 'B');
move_index_list.pop_back();
if ('H' == from)
{
left_count += current_move_count;
}
else
{
left_count -= current_move_count;
}
}
}
else /* 让前一个下船, 换一个尝试 */
{
index = current_move_index % 10 - 1;
Monster & old_checker = monster[index];
old_checker.place = from;
current_total_weight -= old_checker.weight;
current_move_count -= 1;
current_move_index /= 10;
}
++index;
}
change_place(current_move_index, from);
}
void change_place(int current_move_index, char to)
{
while (current_move_index > 0)
{
int index = current_move_index % 10 - 1;
Monster & mover = monster[index];
mover.place = to;
current_move_index /= 10;
}
}
/*
* 显示最优路线
*/
void display_best_way()
{
for (int index = 0; index < 6; ++index)
{
Monster & mst = monster[index];
mst.place = 'H';
}
const char place[3] = { 'H', 'B', 'T' };
int move_base = 1;
bool go = true;
list<int>::iterator iter = best_move_index_list.begin();
while (best_move_index_list.end() != iter)
{
change_place(*iter, place[1]);
display_state(go);
change_place(*iter, place[1 + move_base]);
move_base *= -1;
go = !go;
++iter;
}
}
/*
* 显示当前状态
*/
void display_state(bool go)
{
string here;
here.reserve(16);
string boat;
boat.reserve(16);
boat = "\\_";
string there;
there.reserve(16);
there = "|";
for (int index = 0; index < 6; ++index)
{
const Monster & checker = monster[index];
switch (checker.place)
{
case 'H':
{
here += checker.name;
break;
}
case 'B':
{
boat += checker.name;
break;
}
case 'T':
{
there += checker.name;
break;
}
default:
{
break;
}
}
}
here += "|";
boat += "_/";
int left_distance = (go ? 0 : 8);
string left_water(left_distance, '_');
string right_water(8 - left_distance, '_');
cout << here << left_water
<< boat << right_water
<< there << endl;
}
int main(int argc, char * argv[])
{
move('H', 'T');
if (!best_move_index_list.empty())
{
display_best_way();
cout << " move times: "
<< best_move_index_list.size() << endl;
}
else
{
cout << "there is no solution "
"for this question." << endl;
}
return(0);
}
得到最优方案:
ABC|\_abc_/________|
ABC|________\_a_/|bc
Aa|\_BC_/________|bc
Aa|________\_Bb_/|Cc
Bb|\_Aa_/________|Cc
Bb|________\_Cc_/|Aa
bc|\_BC_/________|Aa
bc|________\_a_/|ABC
|\_abc_/________|ABC
move times: 9
编程解决怪物过河问题

本文讲述了作者在高中时期遇到的怪物过河问题,大学后尝试用编程解决但未能成功,直到最近找到问题抽象方法并成功编码。文章分享了解决过程,展示了不同解法,并指出所实现的解可能非最优。
548

被折叠的 条评论
为什么被折叠?



