怪物过河问题

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

高一的时候,同学(老友)问过这个题,我现在还记得他当时的话语:“这道题,我们那边的一个大学生想了三天都没想出来,呵呵,让我解决了,厉害吧,哈哈。。。”。当时,我也是冲着他那句“大学生三天都没想出来”,才兴致勃勃地答应挑战的,可惜,当时终究是没想出来,最后,只好逼着他讲答案。当然,经历过大学的人都知道,其实大学生也就那样,大多很普通,无须过高的评价。

题意大致如下:

有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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值