一只大桶里盛着16升汽油,要求把它们分成两份,每份9升,可是旁边没有量器, 只有一只能装9升和一只能装5升的空桶,请你利用这3只桶倒来倒去,把汽油平分开来。
cpp
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#define MAX_ST 10000
// 容量数组
const int Cap[3] = {16, 9, 5};
// 桶名指针数组
const char *Bname[3] = {"12升桶", "9升桶", "5升桶"};
/* 结构体记录一次倒油操作:由哪个桶给哪个桶倒的油, **
* 倒了多少油, 三个桶的油量 */
typedef struct {
int st[3];
int pre;
int from;
int to;
int amount;
} Node;
// 定义结构体数组,依次记录所有的倒油操作
Node queue[MAX_ST];
// 队首队尾标记
int head = 0, tail = 0;
// 记录三个桶油量出现与否
bool visited[17][10][6];
// 到达目标状态
bool Is_target(int s[3]);
// 一次倒油操作
bool Pour_oil(int curr[3], int A, int B, int next[3], int *amount);
// 启动倒油操作,用BFS方法寻找最短步数
int bfs(int start[3]);
// 输出平分的步骤
void Print_path(int target_index);
int main() {
int start[3] = {16, 0, 0}; // 初始三个桶的油量
int result;
memset(visited, 0, sizeof(visited));
// 记录有无解决方案
result = bfs(start);
if (result != -1)
{
printf("找到最优解!\n");
Print_path(result);
}
else
{
printf("未找到解决方案\n");
}
return 0;
}
bool Is_target(int s[3])
{
return (s[0] == 8 && s[1] == 8 && s[2] == 0);
}
bool Pour_oil(int curr[3], int A, int B,
int next[3], int *amount)
{
int space;
// 若倒出桶为空,或倒入桶已满,倒油操作失败
if (curr[A] == 0 || curr[B] == Cap[B])
return false;
// next数组赋初值
next[0] = curr[0];
next[1] = curr[1];
next[2] = curr[2];
// 倒入桶能导入的油量
space =Cap[B] - curr[B];
// 确定实际的倒油量,curr[A]=倒出桶的油量
*amount = curr[A] < space ? curr[A] : space;
// 执行倒油操作后,倒出桶和倒入桶的油量
next[A] -= *amount;
next[B] += *amount;
return true; // 实现了一次倒油操作
}
int bfs(int start[3])
{
int i, j;
int next[3];
int amount;
// 三个桶的初始状态放入队尾,-1是初始标志
queue[tail].st[0] = start[0];
queue[tail].st[1] = start[1];
queue[tail].st[2] = start[2];
queue[tail].pre = -1;
queue[tail].from = -1;
queue[tail].to = -1;
queue[tail].amount = 0;
tail++; // 新的位置索引
// 对三个桶的状态做标记
visited[start[0]][start[1]][start[2]] = true;
// 若队列不空
while (head < tail)
{
// 从队首取出结构体
Node *curr = &queue[head];
if (Is_target(curr->st))
{
return head; // 返回目标状态的索引值
}
// 由当前三个桶的状态尝试各种允许的倒油操作
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
if (i == j)
continue;
if (Pour_oil(curr->st, i, j, next, &amount))
{
if (!visited[next[0]][next[1]][next[2]])
{
// 一次倒油操作入队尾
queue[tail].st[0] = next[0];
queue[tail].st[1] = next[1];
queue[tail].st[2] = next[2];
queue[tail].pre = head;
queue[tail].from = i;
queue[tail].to = j;
queue[tail].amount = amount;
tail++;
// 对三个桶的状态做标记
visited[next[0]][next[1]][next[2]] = true;
}
}
}
}
head++; // 新的队首索引
}
return -1; // 没有找到解决方案
}
void Print_path(int target_index)
{
int path[MAX_ST];
int len = 0;
int i;
i = target_index;
// path逆序存放求解步骤索引
while (i != -1)
{
path[len++] = i; // 存索引i
i = queue[i].pre; // 由索引i获得其前驱索引
}
printf("共 %d 步操作:\n", len - 1);
// 逆序输出求解步骤
for (i = len - 1; i >= 0; i--)
{
Node *node = &queue[path[i]]; // 指向状态结构体
if (node->from == -1)
{
printf("初始状态: [16升=%d, 9升=%d, 5升=%d]\n",
node->st[0], node->st[1], node->st[2]);
}
else
{
printf("步骤 %d: 从%s倒%d升到%s\n", len - 1 - i,
Bname[node->from], node->amount,
Bname[node->to]);
printf(" [16升=%d, 9升=%d, 5升=%d]\n",
node->st[0], node->st[1],
node->st[2]);
}
}
}
找到最优解!
共 7 步操作:
初始状态: [16升=16, 9升=0, 5升=0]
步骤 1: 从12升桶倒9升到9升桶
16升=7, 9升=9, 5升=0
步骤 2: 从9升桶倒5升到5升桶
16升=7, 9升=4, 5升=5
步骤 3: 从5升桶倒5升到12升桶
16升=12, 9升=4, 5升=0
步骤 4: 从9升桶倒4升到5升桶
16升=12, 9升=0, 5升=4
步骤 5: 从12升桶倒9升到9升桶
16升=3, 9升=9, 5升=4
步骤 6: 从9升桶倒1升到5升桶
16升=3, 9升=8, 5升=5
步骤 7: 从5升桶倒5升到12升桶
16升=8, 9升=8, 5升=0