开课学院及实验室:计算机学院/电子信息楼 416B 2022年 9 月 28日
学 院 |
计算机科学与网络工程学院 |
年级/专业/班 |
计科 |
姓名 |
Great Macro |
学号 |
|
实验课程名称 |
人工智能原理实验 |
成绩 |
|||||
实验项目名称 |
实验1知识的表示与推理实验 |
指导老师 |
本实验课程是计算机、智能、物联网等专业学生的一门专业课程,通过实验,帮助学生更好地掌握人工智能相关概念、技术、原理、应用等;通过实验提高学生编写实验报告、总结实验结果的能力;使学生对智能程序、智能算法等有比较深入的认识。本实验通过不同知识的表达与推理问题,强化学生对知识表示的了解和应用,为人工智能后续环节的课程奠定基础。
使用C或C++(Visual studio平台等),或其它语言,如matlab或Python。
利用一阶谓词逻辑求解猴子摘香蕉问题:房内有一个猴子,一个箱子,天花板上挂了一串香蕉,其位置如图1所示,猴子为了拿到香蕉,它必须把箱子搬到香蕉下面,然后再爬到箱子上。请定义必要的谓词,列出问题的初始化状态(即下图所示状态),目标状态(猴子拿到了香蕉,站在箱子上,箱子位于位置b)。(附加:从初始状态到目标状态的谓词演算过程。)
图1
定义描述环境状态的谓词。
AT(x,w):x在w处,个体域:x?{monkey},w?{a,b,c,box};
HOLD(x,t):x手中拿着t,个体域:t?{box,banana};
EMPTY(x):x手中是空的;
ON(t,y):t在y处,个体域:y?{b,c,ceiling};
CLEAR(y):y上是空的;
BOX(u):u是箱子,个体域:u?{box};
BANANA(v):v是香蕉,个体域:v?{banana};
使用谓词、连结词、量词来表示环境状态。
问题的初始状态可表示为:So:AT(monkey,a)?EMPTY(monkey)?ON(box,c)?ON(banana,ceiling)?CLEAR(b)?BOX(box)?
BANANA(banana)
要达到的目标状态为:Sg:
AT(monkey,box)?HOLD(monkey,banana)?ON(box,b)?CLEAR(ceiling)?CLEAR(c)?
BOX(box)?BANANA(banana)
从初始状态到目标状态的转化, 猴子需要完成一系列操作, 定义操作类谓词表示其动作。
WALK(m,n):猴子从m走到n处,个体域:m,n?{a,b,c};
CARRY(s,r):猴子在r处拿到s,个体域:r?{c,ceiling},s?{box,banana};
CLIMB(u,b):猴子在b处爬上u;
这3个操作也可分别用条件和动作来表示。条件直接用谓词公式表示,是为完成相应操作所必须具备的条件;当条件中的事实使其均为真时,则可激活操作规则,于是可执行该规则中的动作部分。动作通过前后状态的变化表示,即通过从动作前删除或增加谓词公式来描述动作后的状态。
WALK(m,n):猴子从m走到n处
条件:AT(monkey,m)
动作:
CARRY(s,r):猴子在r处拿到s
条件:AT(monkey,r)?EMPTY(monkey)?ON(s,r)?BOX(box)?BANANA(banana)
动作:
CLIMB(u,b):猴子在b处爬上u
条件:AT(monkey,b)?HOLD(monkey,u)?CLEAR(b)?BOX(box)?BANANA(banana)
动作:
按照行动计划, 一步步进行状态替换, 直至目标状态
AT(monkey,a)?EMPTY(monkey)?ON(box,c)?ON(banana,ceiling)?CLEAR(b)?BOX(box)?
BANANA(banana)
AT(monkey,c)?EMPTY(monkey)?ON(box,c)?ON(banana,ceiling)?CLEAR(b)?BOX(box)?
BANANA(banana)
AT(monkey,c)?HOLD(monkey,box)?ON(banana,ceiling)?CLEAR(b)?CLEAR(c)?BOX(box)?
BANANA(banana)
AT(monkey,b)?HOLD(monkey,box)?ON(banana,ceiling)?CLEAR(b)?CLEAR(c)?BOX(box)?
BANANA(banana)
AT(monkey,box)?EMPTY(monkey)?ON(box,b)?ON(banana,ceiling)?CLEAR(c)?BOX(box)?
BANANA(banana)
AT(monkey,box)?HOLD(monkey,banana)?ON(box,b)?CLEAR(ceiling)?CLEAR(c)?BOX(box)?
BANANA(banana)(目标得解)
猴子行动的规则序列是:WALK(a,c)→CARRY(c,box)→WALK(c,b)→CLIMB(box,b)→
CARRY(banana,ceiling)
参考代码:
#include
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=150) { printf("%s \n", "steplength reached 150,have problem "); return; } for (c=0; c
问题描述:
有n个牧师和n个野人准备渡河,但只有一条能容纳c个人的小船,为了防止野人侵犯牧师,要求无论在何处,牧师的人数不得少于野人的人数(除非牧师人数为0),且假定野人与牧师都会划船,试设计一个算法,确定他们能否渡过河去,若能,则给出小船来回次数最少的最佳方案。
实验步骤:
输入:牧师人数(即野人人数):n;小船一次最多载人量:c。
输出:若问题无解,则显示Failed,否则,显示Successed输出所有可行方案,并标注哪一组是最佳方案。用三元组(X1, X2, X3)表示渡河过程中的状态。并用箭头连接相邻状态以表示迁移过程:初始状态->中间状态->目标状态。
例:当输入n=2,c=2时,输出:221->200->211->010->021->000;
其中:X1表示起始岸上的牧师人数;X2表示起始岸上的野人人数;X3表示小船现在位置(1表示起始岸,0表示目的岸)。
要求:写出算法的设计思想和源程序,并有用户界面实现人机交互(控制台或者窗口都可以),进行输入和输出结果,如:
Please input n: 2 Please input c: 2
Optimal Procedure: 221->200->211->010->021->000
Successed or Failed?: Successed
源程序代码:
#include
#define Yes 1 // monkey on the box #define No 0 // monkey doesn't on the box #define maxStep 150 // monkey can do the max steps // define the problem states struct struct States { char box; // box location char monkey; // monkey location char banana; // banana location char Onbox; // monkey on the box }states[maxStep]; static int n = 0; // count step // initialize problem states void init() { printf("please input the banana location: "); scanf("%c", &states[0].banana); printf("please input the monkey location: "); scanf("\n%c", &states[0].monkey); printf("please input the box location: "); scanf("\n%c", &states[0].box); // if the monkey and box aren't in the same place, // then predicts monkey doesn't on the box if(states[0].monkey != states[0].box) { states[0].Onbox = No; } else { printf("Is the monkey on the box now? (1/0)"); scanf("\n%hd", &states[0].Onbox); } } // monkey reach the banana void reach(int i) { printf("monkey reach the banana\n"); n++; } // monkey go to location void monkeyGoto(int i, char location) { printf("monkey go to %c\n", location); states[i+1] = states[i]; states[i+1].monkey = location; // change monkey location n++; } // monkey climb on the box void climbOnbox(int i) { printf("monkey climb on the box\n"); states[i+1] = states[i]; states[i+1].Onbox = Yes; n++; } // monkey climb down the box void climbDownbox(int i) { printf("monkey climb down the box\n"); states[i+1] = states[i]; states[i+1].Onbox = No; n++; } // monkey move the box to dest void moveBox(int i, char dest) { printf("monkey move the box to %c\n", dest); states[i+1] = states[i]; states[i+1].monkey = dest; // change monkey location states[i+1].box = dest; // change box location n++; } // monkey do next Step int nextStep(int i) { if (i >= maxStep) { printf("Steps are too more, monkey can't go again!/n"); return No; } // if the banana and box are in the same place if(states[i].banana == states[i].box) { // if the monkey is on the box, then reach it if(states[i].Onbox) { reach(i); return Yes; } else { // monkey doesn't on the box but in the same place if(states[i].monkey == states[i].box) { climbOnbox(i); return nextStep(i+1); } // monkey doesn't on the box and not in the same place else { monkeyGoto(i, states[i].box); return nextStep(i+1); } } } // banana and box aren't in the same place else { // monkey on the box if(states[i].Onbox) { climbDownbox(i); return nextStep(i+1); } else { // monkey and box are in the same place if(states[i].monkey == states[i].box) { moveBox(i, states[i].banana); return nextStep(i+1); } // monkey and box aren't int the same place else { monkeyGoto(i, states[i].box); return nextStep(i+1); } } } } int nextStep1(int i) { while(i< maxStep) { // if the banana and box are in the same place if(states[i].banana == states[i].box) { // if the monkey is on the box, then reach it if(states[i].Onbox) { reach(i); return Yes; } else { // monkey doesn't on the box but in the same place if(states[i].monkey == states[i].box) { climbOnbox(i); i++; continue; } // monkey doesn't on the box and not in the same place else { monkeyGoto(i, states[i].box); i++; continue; } } } // banana and box aren't in the same place else { // monkey on the box if(states[i].Onbox) { climbDownbox(i); i++; continue; } else { // monkey and box are in the same place if(states[i].monkey == states[i].box) { moveBox(i, states[i].banana); i++; continue; } // monkey and box aren't int the same place else { monkeyGoto(i, states[i].box); i++; continue; } } } } if (i >= maxStep) { printf("Steps are too more, monkey can't go again!/n"); return No; } } int main(void) { init(); printf("\nmonkey go to grasp the banana now\n\n"); nextStep1(0); printf("\nTo reach the banana, monkey need to do %i steps\n", n); return 0; }
运行结果:
结果分析:
香蕉在B处,猴子在A处,箱子在C处,猴子想摘香蕉则需先到C处,然后推动箱子到B处,然后爬上去,最后摘香蕉。
结果分析:
香蕉在A处,猴子在箱子上,箱子在B处,猴子需要先爬下箱子,然后推动箱子到A处,然后再爬上箱子,最后摘香蕉。
太复杂,而且有bug,无法随便调整猴子,香蕉和箱子的位置。而且伙子摘香蕉的路径不需要保存下来,每当做完一步后就可以打印当前的动作,如果不需要保留状态的话,也不需要用状态数组,只用一个结构体变量就行了。
#include
#include // 定义状态节点 typedef struct node { int src_x; // 起始岸传教士人数 int src_y; // 起始岸野人人数 int dest_x; // 目的岸传教士人数 int dest_y; // 目的岸野人人数 int location; // 船的状态,-1表示在目的岸,1表示在起始岸 struct node *prev; // 前指针 struct node *next; // 后指针 }node; node *head; // 状态链表的头节点 static int N = 0; // 船的最大载人数 static int X = 0; // 起始岸的传教士人数 static int Y = 0; // 起始岸的野人人数 static int count = 0; // 渡河方案 // 一些函数的声明 node *initList(); void del(node *new); int checkSame(node *p); void display(node *head); int checkNo(int X, int Y); void tail_insert(node *head, node *new); void addnew(int x, int y, int location); void Select(int X, int Y, int location); void goRiver(int x, int y, int location); int checkAll(int X, int Y, int location); int main(void) { head = initList(); // 初始化状态链表 if(!head) { printf("初始化状态链表失败\n"); return -1; } printf("请输入起始岸传教士人数:"); scanf("%d", &X); printf("请输入起始岸野人人数:"); scanf("\n%d", &Y); printf("请输入船的最大载人数:"); scanf("\n%d", &N); // 把起始状态插入到链表中 node *new = initList(); new->src_x = X; new->src_y = Y; new->location = 1; tail_insert(head, new); printf("说明:渡河方案表示格式(起始岸传教士人数,起始岸野人人数,目的岸传教士人数,目的岸野人人数,船的状态)\n"); printf("其中船的状态表示格式(-1表示船在目的岸,1表示船在起始岸)\n"); printf("开始计算渡河方案\n"); printf("计算中,请稍等...\n"); Select(X, Y, 1); printf("渡河方案总共有%d种\n", count); return 0; } // 从起始岸或者目的岸选择人渡河,采用深度优先搜索法和剪枝回溯法 void Select(int X, int Y, int location) { int x, y; // x, y满足以下三条不等式 // x <= X 表示选择出的传教士人数不能超过岸上传教士的人数 // y <= Y 表示选择出的野人人数不能超过岸上野人的人数 // x + y <= N, 即y <= N - x, 表示选择出的总人数不能超过上船的最大载人数 for(x = 0; x <= X; x++) { for(y = 0; y <= Y && y <= N-x; y++) { if((x == 0 && y > 0) || (x > 0 && x >= y)) { // 如果是从起始岸选择,则必须至少选出2个人 // 否则一个人划船没意思,会出现死循环 if(location == 1 && x + y >= 2) { goRiver(x, y, location); } // 如果是从目的岸选择,则必须保证选完人上船后,必须有人留在目的岸上 // 否则从起始岸的划船到达目的岸没意思,会出现四循环 if(location == -1 && head->prev->dest_x + head->prev->dest_y - x - y > 0) { goRiver(x, y, location); } } } } // 本次状态搜索完后,要回溯上一个分支(剪枝回溯法) if(head->next->next != head) { del(head->prev); } } // 判断从岸上选出的人能否渡河 void goRiver(int x, int y, int location) { switch(checkAll(x, y, location)) { // 不能渡河 case 0: return; // 可以渡河 case 1: addnew(x, y, location); // 需要进行转态查重 // 具体例子是(2, 1, 0, 1, 1)->(0, 1, 2, 1, -1)->(2, 1, 0, 1, 1)->(0, 1, 2, 1, -1)... // 不去重会出现死循环,不信可以试试2个传教士,2个野人,船最大载人数为2的情况 if(checkSame(head->prev)) { del(head->prev); // 删除重复的状态 return; } // 全部已经渡河 if(!head->prev->src_x && !head->prev->src_y) { printf("第%d种渡河方案\n", ++count); display(head); // 打印渡河方案 del(head->prev); // 剪枝回溯 return; } // 人还没全部渡河,且下一次从起始岸选择人渡河 if(head->prev->location == 1) { Select(head->prev->src_x, head->prev->src_y, 1); } // 人还没全部渡河,且下一次从目的岸选择人渡河 else { Select(head->prev->dest_x, head->prev->dest_y, -1); } return; // 已经全部渡河 // 不过该条件基本不会出现, 因为case1中有判断下一个状态是否为目的态 case 2: printf("第%d种渡河方案\n", ++count); display(head); // 打印渡河方案 del(head->prev); // 剪枝回溯 return; } } // 检查链表中是否有重复的状态 int checkSame(node *p) { for(node *q = head->next; q != p; q = q->next) { // 只需要该状态的起始岸或者目的岸的人数进行比较,不需要两个岸都要进行比较 if(q->src_x == p->src_x && q->src_y == p->src_y && q->location == p->location) { return 1; } } return 0; } // 检查在起始岸或者目的岸的人数是否合法 int checkNo(int x, int y) { return x > 0 && x < y; } // 检查在起始岸和目的岸的人是否合法 int checkAll(int x, int y, int location) { int src_x, src_y, dest_x, dest_y; src_x = head->prev->src_x; src_y = head->prev->src_y; dest_x = head->prev->dest_x; dest_y = head->prev->dest_y; // 只要起始岸或者目的岸的人数不合法,就不能渡河 if(checkNo(src_x - x * location, src_y - y * location) || checkNo(dest_x + x * location, dest_y + y * location)) { return 0; } // 已经全部渡河,不需要再渡河 else if(location == -1 && src_x == 0 && src_y == 0 && dest_x == X && dest_y == Y) { return 2; } // 本次选择的人可以渡河,但未全部渡河 else { return 1; } } // 把新的状态插入到链表中 void addnew(int x, int y, int location) { node *p = initList(); if(!p) { printf("malloc fail\n"); return; } // 修改状态的信息 // 有个小技巧,关于location的,不需要分起始岸或者目的岸写 p->src_x = head->prev->src_x - x * location; p->src_y = head->prev->src_y - y * location; p->dest_x = head->prev->dest_x + x * location; p->dest_y = head->prev->dest_y + y * location; p->location = -head->prev->location; tail_insert(head, p); } // 生成一个状态节点 node *initList() { node *new = malloc(sizeof(node)); if(!new) { printf("malloc fail!\n"); return NULL; } new->src_x = 0; new->src_y = 0; new->dest_x = 0; new->dest_y = 0; new->location = 0; new->prev = new; new->next = new; return new; } // 打印渡河方案 void display(node *head) { // 链表不存在或者为空 if(!head || head->next == head) { return; } for(node *p = head->next; p != head; p = p->next) { printf("%d, %d, %d, %d, %d\n", p->src_x, p->src_y, p->dest_x, p->dest_y, p->location); } printf("\n"); } // 尾插法,把节点插到链表最末 void tail_insert(node *head, node *new) { new->prev = head->prev; new->next = head; head->prev->next = new; head->prev =new; } // 删除一个节点,本程序通常是链表最末的一个 void del(node *new) { new->prev->next = new->next; new->next->prev = new->prev; new->prev = new; new->next = new; free(new); }