野蛮人传教士问题(上)

高级人工智能布置了这个作业,要求用通用结构P=(D,G) D=(S,S0,A,T) G:S->bool表示

S表示合法状态集合,S0是初始状态集合,A是动作集合,T是状态动作转化集合即 T属于SXAXS,G是目标状态判断的布尔函数。

给的是3个野蛮人3个传教士,初步分析可以参见http://www.cnblogs.com/6dan_hust/archive/2010/08/23/1806560.html

采用的是宽度优先搜索,需要存储中间结点,能返回最优解。

OK,先上最终代码:

  1 #include <stdio.h>
2 #include <stdlib.h>
3 #define X 3 //共有三个传教士
4 #define Y 3 //共有三个野蛮人
5 int queue[50][4];
6 //用于存放搜索结点,queue[][0]是左岸传教士人数
7 //queue[][1]是左岸野蛮人人数,queue[][2]是左岸船的数目
8 //queue[][3]用于搜索中的父亲结点序号。
9 int number=1;
10
11 int isSafe(int node[3])
12 {
13 if ((node[0]==0||node[0]==X)&&(node[1]>=0)&&(node[1]<=Y))
14 {
15 return 1;
16 }
17 if ((node[0]==node[1])&&(node[1]>=0)&&(node[1]<=Y))
18 {
19 return 1;
20 }
21 return 0;
22 }
23 int isSuccess(int node[3])
24 {
25 if (node[0]==0&&node[1]==0)
26 {
27 return 1;
28 }
29 else
30 return 0;
31 }
32 int isSame(int node[3],int *tail)
33 {
34 int i,last=*tail;
35 for (i=0;i<last;i++)
36 {
37 if (node[0]==queue[i][0]&&node[1]==queue[i][1]&&node[2]==queue[i][2])
38 {
39 return 1;
40 }
41 }
42 return 0;
43 }
44 int doAction(int *now,int *tail)
45 {
46 int node[3];
47 int nnode[3];
48 int goright[3][2]={{-2,0},{0,-2},{-1,-1}};
49 int goleft[5][2]={{1,1},{1,0},{0,1},{2,0},{0,2}};
50 int i,j;
51 //first与last记录需要展开的结点
52 int first=*now;
53 int last=*tail;
54 printf("第%d次搜索\n",number);
55 for (i=first;i<last;i++)
56 {
57 //获取当前搜索结点
58 node[0]=queue[i][0];
59 node[1]=queue[i][1];
60 node[2]=queue[i][2];
61 printf("展开结点queue[%d],%d,%d,%d\n",i,node[0],node[1],node[2]);
62 if (node[2])//船在左边,过去俩人
63 {
64 for (j=0;j<3;j++)//分别考虑可能的三个动作
65 {
66 nnode[0]=node[0]+goright[j][0];
67 nnode[1]=node[1]+goright[j][1];
68 nnode[2]=0;//船到了右边
69 if (isSafe(nnode))//如果是安全状态,入队
70 {
71 if (!isSame(nnode,tail))//判断与之前展开结点是否相同
72 {
73 queue[*tail][0]=nnode[0];
74 queue[*tail][1]=nnode[1];
75 queue[*tail][2]=nnode[2];
76 queue[*tail][3]=i;//记录父结点
77 (*tail)+=1;//tail指针加1
78 }
79 if (isSuccess(nnode))
80 {
81 return 1;//tail-1的位置就是最终状态结点
82 }
83 }
84 }
85 }else{//船在右边,过来一人或俩人
86 for (j=0;j<5;j++)//分别考虑可能的5个动作
87 {
88 nnode[0]=node[0]+goleft[j][0];
89 nnode[1]=node[1]+goleft[j][1];
90 nnode[2]=1;//船回到左边
91 if (isSafe(nnode)&&!isSame(nnode,tail))//如果是安全状态且与之间状态不同 入队
92 {
93 queue[*tail][0]=nnode[0];
94 queue[*tail][1]=nnode[1];
95 queue[*tail][2]=nnode[2];
96 queue[*tail][3]=i;//记录父结点
97 (*tail)+=1;//tail指针加1
98 }
99 }
100 }
101 (*now)+=1;//now指针加1
102 }
103 number++;
104 return 0;
105 }
106
107 int main()
108 {
109 int now=0,tail=1,i;//now指示当前搜索结点
110 queue[0][0]=X;
111 queue[0][1]=Y;
112 queue[0][2]=1;
113 queue[0][3]=-1;//用于返回搜索路程时,初始结点的标记
114 while(!doAction(&now,&tail))
115 {
116 }
117 tail-=1;
118 printf("找到的解为\n");
119 do
120 {
121 printf("%d,%d,%d,%d\n",queue[tail][0],queue[tail][1],queue[tail][2],queue[tail][3]);
122 tail=queue[tail][3];
123 } while (queue[tail][3]>=0);
124 printf("%d,%d,%d,%d\n",queue[0][0],queue[0][1],queue[0][2],queue[0][3]);
125 return 0;
126 }

结果:

我做的主要工作有:

1.合法状态检验

由题意知道,若x>0,则x>y,若x<3,则3-x>3-y。则在3>x>0时有x=y。

2.动作集优化

由现实意义可知,在最优解中,从左边到右边的船一定是满载2个人。

我没有给出严格证明,最近时间比较紧,如果你感兴趣的话,可以试试。

3.重复状态检验

由于渡船动作的可逆性,使得动作状态转换图中出现很多环。

为了提高算法性能,必须进行重复状态搜索检验。


如果有时间的话我会写 野蛮人传教士问题(下)

主要考虑一般化的传教士野蛮人问题及性能问题。

1传教士人数

2野蛮人人数

3船数,船载

4由于目标状态已知,可以采用双向搜索

5启发式搜索等对一般问题进行性能优化

6....

原文地址:https://www.cnblogs.com/2010Freeze/p/2388466.html