三对阿拉伯夫妇要过河,一只小船只能载二人。丈夫不在场的情况下,妻子身边不能有其他男人。如何过河?
算法
简单明了,回溯法。
数据结构
先定义数据结构。
//三对夫妇,一共六人 #define COUPLE_COUNT 3 struct riverside { bool _has_boat; char _person[COUPLE_COUNT * 2]; }; riverside left, right;
第一个布尔变量表示船是否在岸边,如果岸边没有船,那么不准过河。
第二个数组是一个位图,对应下标如果是1,表示当前此人在河岸这边,为0,则不在这边。
一开始,所有夫妇在左岸,这个数组全为一。下标为偶数的元素是丈夫,下标为奇数的下标是妻子。
如果从左岸过去第一个妻子和第二个妻子,那么数组变为以下状态:
与此同时,右岸的数组变为:
过河策略
每个人有在和不在两种状态,一共有六人,那么要从左岸挑选过河的人选,最多有2的6次方也就是64种方式。如果可以过去的话,船运行的次数不会超过64。
从0到63,每个数字代表一个过河策略。我们只要把这个数字转换成二进制形式,就可以得到一个过河方法。比如,数字5,转换成二进制就是000101,为了编程方便,我们将其倒置,也就是101000,这就表示要让第一个丈夫和第二个丈夫过河。
我们要遍历所有方法,只要从1便利到63即可,0表示一个人也不运,不用考虑。当然,这里肯定有很多违规的过河方法,我们要把它们丢掉。
主函数
伪代码如下:
void cross_river(riverside from, riverside to) { for (int i = 0; I < 63; i++) { if (!can_cross(I, from, to)) continue; else { Cross(); //开船,走一趟 If (Succeed()) { Printf(“ok!!”); Exit(0); //如果过河成功,直接退出,简单粗暴 } Cross_river(to, from); //递归调用,注意,交换一下变量。从左到右,然后从右到左。 } } }
can_cross函数,是我们重点要实现的函数。
他由以下几个部分组成。
先写判断河岸边众人能否共处的函数。
bool riverside::sharia_judge() { for (int i = 0; i < COUPLE_COUNT; i++) { if (_person[i * 2 + 1] != 0) // 如果河岸有妻子 { if (_person[i * 2] != 0) // 如果她丈夫也在,ok { continue; } else //如果她丈夫不在 { for (int j = 0; j < COUPLE_COUNT; j++) //遍历所有的丈夫 if (_person[j * 2] != 0) // 如果有其他丈夫。走到这个分之,就不会有她丈夫了 { return false; } } } } return true; }
不走回头路
为了避免刚把一个人运过去,又把这人运回来,进而无限循环下去的场景。我们在过河前,需要记住当前河岸边都有哪些人,船是否在,下次过河的时候要判断一下,如果状态又回到之前了,那么这种过河方式就不允许。
过河策略的选择
过河策略是一个数字。代码不贴了,需要判断以下几点:
(1) 岸边没有船,不能过。
(2) 要运的人不在,这个策略丢弃。
(3) 要运的人超过两个,这个策略丢弃。不能超载。
(4) 运送的人是一男一女,如果不是夫妻,这个策略丢弃。
(5) 如果过河后,又回到之前的状态了,这一步不能走。
具体参见代码。
直接贴结果。
#include <stdio.h> #include <set> #include <stdlib.h> #include <vector> using namespace std; #define COUPLE_COUNT 3 struct riverside { bool _has_boat; // 这个变量似乎没用 char _person[COUPLE_COUNT * 2]; riverside() { _has_boat = true; memset(_person, 1, COUPLE_COUNT * 2); } int count() { int i = 0; int ret = 0; for (; i < COUPLE_COUNT * 2; i++) { if (_person[i] == 1) { ret++; } } return ret; } void print() const { char *person = "AaBbCcDdEe"; for (int i = 0; i < COUPLE_COUNT * 2; i++) { if (_person[i] != 0) { if (i % 2 == 0) printf("%c", person[i]); else printf("%c", person[i]); } else { printf("."); } } } bool sharia_judge() { int shangren = 0; int yeren = 0; for (int i = 0; i < COUPLE_COUNT; i++) { if (_person[i * 2] != 0) { shangren++; } if (_person[i * 2 + 1]) { yeren++; } } return shangren >= yeren; } }; // 操作符要重载一下,不然没法用 set<> bool operator < (const riverside& left, const riverside& right) { if (left._has_boat == false && right._has_boat == true) { return true; } for (int i = 0; i < COUPLE_COUNT * 2; i++) { if (left._person[i] < right._person[i]) { return true; } if (left._person[i] > right._person[i]) { return false; } } return false; } bool operator == (const riverside& left, const riverside& right) // 这个函数没用到 { return memcmp(&left, &right, sizeof(left)) == 0; } set<riverside> g_history_left; // 左岸所有的状态,不能走回头路 set<riverside> g_history_right; // 右岸所有的状态,不能走回头路 // 一条过河记录 struct record { int _no; riverside _from; riverside _to; }; vector<record> g_records; // 这个堆栈记录过河记录 void print(const riverside &from, const riverside& to, int no) { printf("[%02d] ", no); const riverside* left, *right; if (no % 2 == 1) { left = &from; right = &to; } else { left = &to; right = &from; } left->print(); if (left->_has_boat) { printf("|~<||>~"); } else { printf("|~~~~~~"); } printf("~~~~~~~~~~~~~"); if (right->_has_boat) { printf("~<||>~|"); } else { printf("~~~~~~|"); } right->print(); printf("\n"); } void print_records() // 过河完毕,把过河记录打印出来 { int i = 0; for (; i < g_records.size(); i++) { const record& rcd = g_records[i]; print(rcd._from, rcd._to, rcd._no); } } int no = 0; // 第几趟过河 void covert(int index, int arry[]) // 把一个整数转换成二进制表达式,不过是倒过来的 { for (int i = 0; i < COUPLE_COUNT * 2; i++) { arry[i] = index % 2; index /= 2; } } bool can_cross(int first, int second, riverside from, riverside to) { if (!from._has_boat) { return false; } if (first >= 0) { from._person[first] = 0; to._person[first] = 1; } if (second >= 0) { from._person[second] = 0; to._person[second] = 1; } from._has_boat = false; to._has_boat = true; if (!from.sharia_judge() || !to.sharia_judge()) { return false; } if (no % 2 == 0) // 从右到左渡河,判断这次过河后,是不是又走回老路了. { if (g_history_left.find(to) != g_history_left.end()) { return false; } } else // 从左到右渡河,判断过河后,是不是又走回老路了 { if (g_history_right.find(to) != g_history_right.end()) { return false; } } return true; } bool can_cross(int index, const riverside& from, const riverside &to, int &first, int &second) { int arry[COUPLE_COUNT * 2]; covert(index, arry); int i = 0; int person_count = 0; for (i = 0; i < COUPLE_COUNT * 2; i++) { if (arry[i] == 1) { person_count++; } } if (person_count > 2) // 超载了 { return false; } int person_in_boat[2] = {-1, -1}; int j = 0; for (i = 0; i < COUPLE_COUNT * 2; i++) { if (arry[i] == 1) { if (from._person[i] == 0) { return false; // 岸边没这个人 } person_in_boat[j++] = i; } } first = person_in_boat[0]; second = person_in_boat[1]; return can_cross(person_in_boat[0], person_in_boat[1], from, to); } void cross(int first, int second, riverside& from, riverside& to) { if (no % 2 == 1) // 从左到右,记录下来过河前的状态 { g_history_left.insert(from); } else // 从右到左,记录下过河前的状态 { g_history_right.insert(from); } if (first >= 0) { from._person[first] = 0; to._person[first] = 1; } if (second >= 0) { from._person[second] = 0; to._person[second] = 1; } from._has_boat = false; to._has_boat = true; record rcd; rcd._no = no; rcd._from = from; rcd._to = to; g_records.push_back(rcd); } int total_way = 1 << (2 * COUPLE_COUNT); // 一共 2 的 6 次方 种过河方案 void cross_river(riverside from, riverside to) { no++; if (no % 2 == 1 && from.count() == 0) // 左岸已经没人了? { print_records(); exit(12); } for (int i = 1; i < total_way; i++) { int first, second; if (!can_cross(i, from, to, first, second)) { continue; } else { riverside f = from; // 把状态记录下来 riverside t = to; cross(first, second, from, to); if (no % 2 == 1 && from.count() == 0) // 左岸已经没人了? { print_records(); exit(12); } cross_river(to, from); from = f; to = t; } } g_records.pop_back(); no--; } int main() { riverside left, right; memset(&left, 1, sizeof(left)); left._has_boat = true; memset(&right, 0, sizeof(right)); right._has_boat = false; cross_river(left, right); printf("no way!\n\n"); }