【C++】人工智慧實驗一之猴子摘香蕉/傳教士與野人(含完整程式碼與狀態遷移圖)
文章目錄
一、猴子摘香蕉問題
1、問題描述
利用一階謂詞邏輯求解猴子摘香蕉問題:房內有一個猴子,一個箱子,天花板上掛了一串香蕉,其位置如圖1所示,猴子為了拿到香蕉,它必須把箱子搬到香蕉下面,然後再爬到箱子上。請定義必要的謂詞,列出問題的初始化狀態(即下圖所示狀態),目標狀態(猴子拿到了香蕉,站在箱子上,箱子位於位置b)。
(附加:從初始狀態到目標狀態的謂詞演算過程。)
2、解題思路
猴子按照先到箱子所在位置/從箱子上爬下來→把箱子搬到香蕉下面→爬上箱子摘香蕉的邏輯進行著。
所以需要編寫四個行動邏輯——走到箱子所在位置、從箱子上爬下來、把箱子搬到香蕉上面、爬上箱子摘香蕉。
使用一個結構定義猴子、箱子、香蕉、相對箱子的位置狀態——
猴子在A點則標-1,猴子在B點則標0,猴子在C點則標1
箱子在A點則標-1,箱子在B點則標0,箱子在C點則標1
香蕉在A點則標-1,香蕉在B點則標0,香蕉在C點則標1
猴子爬上箱子則標1,沒爬上則標-1
struct State
{
int monkey; /*-1:Monkey at A;0: Monkey at B;1:Monkey at C;*/
int box; /*-1:box at A;0:box at B;1:box at C;*/
int banana; /*Banana at B,Banana=0*/
int monbox; /*-1: monkey on the box;1: monkey the box;*/
};
struct State States[150];
輸入一個初始狀態(a, b, c, d)
根據問題,確定終止狀態是猴子摘到香蕉{(x,x,x,0)}(x 屬於 {0,-1, 1})。
使用遞迴呼叫的方式搜尋路徑,每次遞迴前先判斷當前狀態是否與之前的狀態重複,若重複則認為形成一個環路,回到上一步尋找其他方式通往新的狀態。
3、實驗結果及分析
實驗結果一
分析:
初始時,猴子站在A位置,箱子在C位置,香蕉在B位置,猴子沒有站在箱子上。
猴子摘香蕉的步驟如下:
猴子走去C位置→猴子把箱子從C位置搬到B位置→猴子爬上箱子→猴子摘到香蕉
實驗結果二
分析:
初始時,猴子站在A位置,箱子在B位置,香蕉在B位置,猴子沒有站在箱子上。
猴子摘香蕉的步驟如下:
猴子走去B位置→猴子爬上箱子→猴子摘到香蕉
實驗結果三
分析:
初始時,猴子站在A位置,箱子在A位置,香蕉在B位置,猴子站在箱子上。
猴子摘香蕉的步驟如下:
猴子從箱子上爬下來→猴子把箱子從A位置搬到B位置→猴子爬上箱子→猴子摘到香蕉
4、實驗結果
當傳教士與野人為五人,船最多允許三人過河時,程式執行結果如下
解的狀態遷移圖
1、550->441->440->331->330->221->220->111->110->001
2、550->441->440->331->330->221->220->011->110->001
3、550->441->540->331->330->221->220->111->110->001
4、550->441->540->331->330->221->220->011->110->001
5、實驗程式碼
#include "stdafx.h"
#include<string.h>
#include<iostream>
#include <stdio.h>
using namespace std;
struct State
{
int monkey; /*-1:Monkey at A;0: Monkey at B;1:Monkey at C;*/
int box; /*-1:box at A;0:box at B;1:box at C;*/
int banana; /*Banana at B,Banana=0*/
int monbox; /*-1: monkey on the box;1: monkey the box;*/
};
struct State States[150];
char* routesave[150];
/*function monkeygoto,it makes the monkey goto the other place*/
void monkeygoto(int b, int i)
{
int a;
a = b;
if (a == -1)
{
routesave[i] = "Monkey go to A";
States[i + 1] = States[i];
States[i + 1].monkey = -1;
}
else if (a == 0)
{
routesave[i] = "Monkey go to B";
States[i + 1] = States[i];
States[i + 1].monkey = 0;
}
else if (a == 1)
{
routesave[i] = "Monkey go to C";
States[i + 1] = States[i];
States[i + 1].monkey = 1;
}
else
{
printf("parameter is wrong");
}
}
/*end function monkeyygoto*/
/*function movebox,the monkey move the box to the other place*/
void movebox(int a, int i)
{
int B;
B = a;
if (B == -1)
{
routesave[i] = "monkey move box to A";
States[i + 1] = States[i];
States[i + 1].monkey = -1;
States[i + 1].box = -1;
}
else if (B == 0)
{
routesave[i] = "monkey move box to B";
States[i + 1] = States[i];
States[i + 1].monkey = 0;
States[i + 1].box = 0;
}
else if (B == 1)
{
routesave[i] = "monkey move box to C";
States[i + 1] = States[i];
States[i + 1].monkey = 1;
States[i + 1].box = 1;
}
else
{
printf("parameter is wrong");
}
}
/*end function movebox*/
/*function climbonto,the monkey climb onto the box*/
void climbonto(int i)
{
routesave[i] = "Monkey climb onto the box";
States[i + 1] = States[i];
States[i + 1].monbox = 1;
}
/*function climbdown,monkey climb down from the box*/
void climbdown(int i)//如果初始狀態猴子在箱子上,則需要爬下來
{
routesave[i] = "Monkey climb down from the box";
States[i + 1] = States[i];
States[i + 1].monbox = -1;
}
/*function reach,if the monkey,box,and banana are at the same place,the monkey reach banana*/
void reach(int i)
{
routesave[i] = "Monkey reach the banana";
}
/*output the solution to the problem*/
void showSolution(int i)//列印
{
int c;
printf("%s \n", "Result to problem:");
for (c = 0; c<i + 1; c++)
{
printf("Step %d : %s \n", c + 1, routesave[c]);
}
printf("\n");
}
/*perform next step*/
void nextStep(int i)
{
int c;
int j;
//超過一定步數,判斷為有問題
if (i >= 150)
{
printf("%s \n", "steplength reached 150,have problem ");
return;
}
//判斷是否跟之前的狀態相同,若相同則可能陷入迴圈,需要退出
for (c = 0; c<i; c++) /*if the current state is same to previous,retrospect*/
{
if (States[c].monkey == States[i].monkey&&States[c].box == States[i].box&&States[c].banana == States[i].banana&&States[c].monbox == States[i].monbox)
return;
}
//成功拿到香蕉
if (States[i].monbox == 1 && States[i].monkey == 0 && States[i].banana == 0 && States[i].box == 0)
{
showSolution(i);
exit(0);
}
j = i + 1;//進行資料更新,用來標記當前是第幾個狀態
if (States[i].monkey == 0)//猴子站在了位置0
{
if (States[i].box == 0)
{
if (States[i].monbox == -1)
{
climbonto(i);
reach(i + 1);
nextStep(j);
}
else
{
reach(i + 1);
nextStep(j);
}
}
else
{
monkeygoto(States[i].box, i);
nextStep(j);
movebox(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
}
/*end if*/
if (States[i].monkey == -1)
{
if (States[i].box == -1)
{
if (States[i].monbox == -1)
{
movebox(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
else
{
climbdown(i);
nextStep(j);
movebox(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
}
else if (States[i].box == 0)
{
monkeygoto(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
else
{
monkeygoto(1, i);
nextStep(j);
movebox(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
}
/*end if*/
if (States[i].monkey == 1)
{
if (States[i].box == 1)
{
if (States[i].monbox == -1)
{
movebox(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
else
{
climbdown(i);
nextStep(j);
movebox(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
}
else if (States[i].box == -1)
{
monkeygoto(-1, i);
nextStep(j);
movebox(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
else
{
monkeygoto(0, i);
nextStep(j);
climbonto(i);
reach(i + 1);
nextStep(j);
}
}
/*end if*/
}/*end nextStep*/
int main()
{
States[0].monkey = -1;
States[0].box = 1;
States[0].banana = 0;
States[0].monbox = -1;
nextStep(0);
}
二、傳教士(牧師)與野人問題
1、問題描述
有n個牧師和n個野人準備渡河,但只有一條能容納c個人的小船,為了防止野人侵犯牧師,要求無論在何處,牧師的人數不得少於野人的人數(除非牧師人數為0),且假定野人與牧師都會划船,試設計一個演算法,確定他們能否渡過河去,若能,則給出小船來回次數最少的最佳方案。
2、實驗步驟
輸入:牧師人數(即野人人數):n;小船一次最多載人量:c。
輸出:若問題無解,則顯示Failed,否則,顯示Successed輸出所有可行方案,並標註哪一組是最佳方案。用三元組(X1, X2, X3)表示渡河過程中的狀態。並用箭頭連線相鄰狀態以表示遷移過程:初始狀態->中間狀態->目標狀態。
例:當輸入n=2,c=2時,輸出:221->200->211->010->021->000;
其中:X1表示起始岸上的牧師人數;X2表示起始岸上的野人人數;X3表示小船現在位置(1表示起始岸,0表示目的岸)。
3、實驗要求
寫出演算法的設計思想和源程式,並有使用者介面實現人機互動(控制檯或者視窗都可以),進行輸入和輸出結果,如:
Please input n: 2 Please input c: 2
Optimal Procedure: 221->200->211->010->021->000
Successed or Failed?: Successed
4、解題思路
針對“傳教士與野人”實驗,輸入不同的傳教士與野人數目,允許過河的最大人數,可以得到不同的結果。在輸出所有可行路徑之後,輸出最優路徑,即所花次數最少的結果。
這題使用DFS演算法,運用遞迴來寫DFS演算法,搜尋掃描可能的路徑,若可行則列印出來,同時比較當前路徑是否比已儲存的最短路徑短,若是,則當前路徑儲存為最短路徑,若否則跳過。
5、實驗程式碼
// 傳教士與野人.cpp
#include <iostream>
using namespace std;
#define maxNum 150
struct op
{
int M; //牧師過河人數
int C; //野人過河人數
};
struct State
{
int minister; //起始岸上的牧師人數
int savage; //起始岸上的野人人數
int side; //side=0,船在初始岸,side=1,船在對岸
};
int n; //牧師和野人數目
int c; //小船最多能載的人數
int op_num; //有多少種過河方式
int min_road=999;//最短路徑
struct op opNum[maxNum];
struct State States[maxNum];
struct State StatesMin[maxNum];
//安全狀態
int isSafe(int i)
{
if (States[i].minister == 0 || States[i].minister == n || States[i].minister == States[i].savage)
return 1;
return 0;
}
//最終目標
int isGoal(int i)
{
if (States[i].minister == 0 && States[i].savage == 0)
return 1;
return 0;
}
//判斷是否跟之前的狀態重複
int isRepeat(int i)
{
for (int j = 0; j < i; j++)
{
if (States[i].minister == States[j].minister&&States[i].savage == States[j].savage&&States[i].side==States[j].side)
return 1;
}
return 0;
}
//可選擇的過河方式
void OpNum()
{
for(int i=0;i<=c;i++)
for (int j = 0; j <= i&&j<=c-i; j++)
{
opNum[op_num].M = i;
opNum[op_num++].C = j;
}
}
//儲存最短的過河方式
void MinWay(int i)
{
for (int j = 0; j <= i; j++)
{
StatesMin[j].minister = States[j].minister;
StatesMin[j].savage = States[j].savage;
StatesMin[j].side = States[j].side;
}
}
//列印
void Print(State *state,int i)
{
for (int j = 0; j < i; j++)
{
cout << state[j].minister << state[j].savage << state[j].side<<"->";
if (j + 1 % 10 == 0)
cout << endl;
}
cout << state[i].minister << state[i].savage << state[i].side <<endl<<endl;
}
//使用dfs遍歷尋找解
void nextStep(int i)
{
// 遞迴出口
if (isGoal(i))
{
if (i < min_road)
{
min_road = i;
MinWay(i);
}
cout << "Successed:" << endl;
Print(States,i);
return;
}
// 是否安全
if (!isSafe(i))
return;
// 是否重複
if (isRepeat(i))
return;
int j = i + 1;
// 起始岸
if (States[i].side == 0)
{
for (int k = 0; k < op_num; k++)
{
if (opNum[k].M > States[i].minister || opNum[k].C > States[i].savage)
continue;
States[j].minister = States[i].minister-opNum[k].M;
States[j].savage = States[i].savage - opNum[k].C;
States[j].side = 1;
nextStep(j);
}
}
else//對岸
{
for (int k = 0; k < op_num; k++)
{
if (opNum[k].M > (n-States[i].minister) || opNum[k].C > (n-States[i].savage))
continue;
States[j].minister = States[i].minister+opNum[k].M;
States[j].savage = States[i].savage+opNum[k].C;
States[j].side = 0;
nextStep(j);
}
}
}
int main()
{
cout << "Please input n: ";
cin >> n;
cout << "Please input c: ";
cin >> c;
States[0].minister = n;
States[0].savage = n;
States[0].side = 0;
OpNum();
nextStep(0);
if (min_road < 999)
{
cout << "Optimal Procedure:" << endl;
Print(StatesMin, min_road);
}
else
{
cout << "Fail." << endl;
}
return 0;
}
相關文章
- PV 與 PVC 狀態遷移
- 猴子分香蕉
- 傳教士與食人者問題pythonPython
- 傳統 Web 框架部署與遷移Web框架
- 042-HTTP協議之方法與狀態碼HTTP協議
- React專案實踐(二)一個登入頁面的狀態遷移React
- TCP 的連線建立與關閉狀態及資料傳輸通訊過程【含有 PHP 測試實驗程式碼】TCPPHP
- 程式的狀態與轉換
- Cloudflare 從 PHP 到 Go:遷移與經驗分享CloudPHPGo
- Jenkins搭建與資料遷移實踐Jenkins
- 一文讀懂所有HTTP狀態碼含義HTTP
- Linux程式狀態——top,ps中看到程式狀態D,S的含義Linux
- echarts遷移圖動態載入Echarts
- echarts之靜態與動態地圖Echarts地圖
- 狀態模式(c++實現)模式C++
- 前端狀態管理與有限狀態機前端
- 一個有限狀態機的C++實現C++
- 營銷演算法之爭落幕 簡化程式碼與遷移學習成最大贏家演算法遷移學習
- git倉庫完整遷移Git
- 《神經網路的梯度推導與程式碼驗證》之LSTM前向和反向傳播的程式碼驗證神經網路梯度反向傳播
- SpringBoot系列——狀態機(附完整原始碼)Spring Boot原始碼
- Vuex 4與狀態管理實戰指南Vue
- 深度學習之遷移學習介紹與使用深度學習遷移學習
- 資料庫平滑遷移方案與實踐分享資料庫
- 第二次實驗完整程式碼
- React原始碼分析與實現(二):狀態、屬性更新 -> setStateReact原始碼
- 淺談TCP(1):狀態機與重傳機制TCP
- 《神經網路的梯度推導與程式碼驗證》之vanilla RNN前向和反向傳播的程式碼驗證神經網路梯度RNN反向傳播
- React 狀態管理:狀態與生命週期React
- [服務端與網路]http協議與http狀態碼服務端HTTP協議
- PostgreSQL狀態變遷SQL
- 《神經網路的梯度推導與程式碼驗證》之CNN前向和反向傳播過程的程式碼驗證神經網路梯度CNN反向傳播
- Android Activity 重建之狀態儲存與恢復Android
- 劉思含實驗一
- 11.23實驗 22:狀態模式模式
- 一次基於AST的大規模程式碼遷移實踐AST
- Percona XtraDB Cluster高可用與狀態快照傳輸(PXC 5.7 )
- 《神經網路的梯度推導與程式碼驗證》之FNN(DNN)前向和反向傳播過程的程式碼驗證神經網路梯度DNN反向傳播