广州大学人工智能原理实验一:知识的表示与推理实验

相关资料

广州大学人工智能原理实验一:知识的表示与推理实验
广州大学人工智能原理实验二:八数码问题
广州大学人工智能原理实验三:产生式系统推理
广州大学人工智能原理实验四:TSP问题的遗传算法实现
广州大学人工智能原理实验五:基于汉诺塔的问题规约图实现
五份实验报告下载链接

用谓词表示农夫、狼、山羊、白菜问题

      • 相关资料
    • 一、实验目的
    • 二、基本要求
    • 三、实验软件
    • 四、实验内容:
      • (一)猴子摘香蕉问题
      • (二)传教士(牧师)与野人问题
    • 五、实验源代码
      • (一)猴子摘香蕉问题
      • (二)传教士(牧师)与野人问题
    • 六、实验结果及分析
    • 七、最佳优先搜索BFS求解传教士(牧师)与野人问题

一、实验目的

本实验课程是计算机、智能、物联网等专业学生的一门专业课程,通过实验,帮助学生更好地掌握人工智能相关概念、技术、原理、应用等;通过实验提高学生编写实验报告、总结实验结果的能力;使学生对智能程序、智能算法等有比较深入的认识。本实验通过牧师与野人渡河的问题,强化学生对知识表示的了解和应用,为人工智能后续环节的课程奠定基础。

二、基本要求

1.实验前,复习《人工智能》课程中的有关内容。
2.准备好实验数据。
3.编程或验证需要独立完成,程序应加适当的注释。
4.完成实验报告。

三、实验软件

使用C或C++(Visual studio平台等),或其它语言,如matlab或Python。

四、实验内容:

(一)猴子摘香蕉问题

利用一阶谓词逻辑求解猴子摘香蕉问题:房内有一个猴子,一个箱子,天花板上挂了一串香蕉,其位置如图1所示,猴子为了拿到香蕉,它必须把箱子搬到香蕉下面,然后再爬到箱子上。请定义必要的谓词,列出问题的初始化状态(即下图所示状态),目标状态(猴子拿到了香蕉,站在箱子上,箱子位于位置b)。(附加:从初始状态到目标状态的谓词演算过程。)
广州大学人工智能原理实验一:知识的表示与推理实验_第1张图片

图1

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};

2.使用谓词、连结词、量词来表示环境状态。
问题的初始状态可表示为: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©?
BOX(box)?BANANA(banana)

3.从初始状态到目标状态的转化, 猴子需要完成一系列操作, 定义操作类谓词表示其动作。
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)
动作:在这里插入图片描述

4.按照行动计划, 一步步进行状态替换, 直至目标状态
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©?BOX(box)?
BANANA(banana)
AT(monkey,b)?HOLD(monkey,box)?ON(banana,ceiling)?CLEAR(b)?CLEAR©?BOX(box)?
BANANA(banana)
AT(monkey,box)?EMPTY(monkey)?ON(box,b)?ON(banana,ceiling)?CLEAR©?BOX(box)?
BANANA(banana)
AT(monkey,box)?HOLD(monkey,banana)?ON(box,b)?CLEAR(ceiling)?CLEAR©?BOX(box)?
BANANA(banana)(目标得解)
猴子行动的规则序列是:WALK(a,c)→CARRY(c,box)→WALK(c,b)→CLIMB(box,b)→
CARRY(banana,ceiling)

(二)传教士(牧师)与野人问题

问题描述:
有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 
#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 NOT on the box;1: monkey on the box;*/
};
struct State States[150]; //记录步骤状态
char* routesave[150];


int Islegal(int num); /*判断输入的位置是否合法*/
void monkeygoto(int b, int i);/*移动函数*/
void movebox(int a, int i);/*推箱子*/
void climbonto(int i);/*爬上箱子*/
void climbdown(int i);/*跳下箱子*/
void reach(int i);/*摘到香蕉*/
void showSolution(int i);/*打印输出路线*/
void nextStep(int i); /*下一步,递归*/

int main()
{
     
	//初始化状态
	States[0].monkey = -1;  //猴子的初始位置
	States[0].box = 1;		//盒子的初始位置
	States[0].banana = 0;	//香蕉的初始位置
	States[0].monbox = -1;	//猴子和箱子是否爬上了箱子
	int a = Islegal(0);		//判断输入是否合法
	if (a)nextStep(0);
	else printf("输入不合法 \n");
}


/*判断输入的位置是否合法*/
int Islegal(int num)
{
     
	if (States[num].monkey < -1 || States[num].monkey >1)
		return 0;

	if (States[num].box < -1 || States[num].box >1)
		return 0;

	if (States[num].banana < -1 || States[num].banana >1)
		return 0;

	if (States[num].monbox !=-1 && States[num].monbox !=1)
		return 0;

	if (States[num].monbox == 1 && (States[num].monkey != States[num].box)) //猴子和箱子不在同一个位置上,不可能在箱子上的
		return 0;

	return 1;
}

/*移动函数*/
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");
	}
}

/*推箱子*/
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");
	}
}

/*爬上箱子*/
void climbonto(int i)
{
     
	routesave[i] = "Monkey climb onto the box";
	States[i + 1] = States[i];
	States[i + 1].monbox = 1;
}

/*跳下箱子*/
void climbdown(int i)
{
     
	routesave[i] = "Monkey climb down from the box";
	States[i + 1] = States[i];
	States[i + 1].monbox = -1;
}

/*摘到香蕉*/
void reach(int i)
{
     
	routesave[i] = "Monkey reach the banana";
}

/*打印输出路线*/
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");
}

/*下一步,递归*/
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 (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 == States[i].box && States[i].banana == States[i].monkey)  //这种情况说明摘到了香蕉,直接打印路径
	{
     
		reach(i);
		showSolution(i);
		printf("Press any key to continue \n");
		getchar();
		return;
	}
	j = i + 1;
	if (States[i].monkey == 0)   //针对猴子在0位置,盒子在另外三个位置的条件判断
	{
     
		if (States[i].box == 0) //香蕉和盒子合一,判断是否需要爬上去
		{
     
			if (States[i].monbox == -1)
			{
     
				climbonto(i);
				nextStep(j);
				monkeygoto(-1, i);
				nextStep(j);
				monkeygoto(1, i);
				nextStep(j);
				movebox(-1, i);
				nextStep(j);
				movebox(1, i);
				nextStep(j);
			}
			else
			{
     
				climbdown(i);
				nextStep(j);
			}
		}
		else  //盒子在别的位置
		{
     
			monkeygoto(-1, i);
			nextStep(j);
			monkeygoto(1, i);
			nextStep(j);
		}
	}


	if (States[i].monkey == -1)
	{
     
		if (States[i].box == -1)
		{
     
			if (States[i].monbox == -1)
			{
     
				climbonto(i);
				nextStep(j);
				monkeygoto(0, i);
				nextStep(j);
				monkeygoto(1, i);
				nextStep(j);
				movebox(0, i);
				nextStep(j);
				movebox(1, i);
				nextStep(j);
			}
			else
			{
     
				climbdown(i);
				nextStep(j);
			}
		}
		else
		{
     
			monkeygoto(0, i);
			nextStep(j);
			monkeygoto(1, i);
			nextStep(j);
		}
	}

	if (States[i].monkey == 1)
	{
     
		if (States[i].box == 1)
		{
     
			if (States[i].monbox == -1)
			{
     
				climbonto(i);
				nextStep(j);
				monkeygoto(0, i);
				nextStep(j);
				monkeygoto(-1, i);
				nextStep(j);
				movebox(0, i);
				nextStep(j);
				movebox(-1, i);
				nextStep(j);
			}
			else
			{
     
				climbdown(i);
				nextStep(j);
			}
		}
		else
		{
     
			monkeygoto(0, i);
			nextStep(j);
			monkeygoto(-1, i);
			nextStep(j);
		}
	}
}

(二)传教士(牧师)与野人问题

#include 
#include 
#include 

typedef struct State						//描述问题状态
{
     
	int mLeft;								//左岸传教士数量
	int cLeft;								//左岸野人数量
	int mRight;								//右岸传教士数量
	int cRight;								//右岸野人数量
	int b;									//船在左岸还是右岸
};
struct State States[150];				    //记录步骤状态
char* routesave[150];

typedef struct Move							//描述最优路线
{
     
	char ch[1000]="";						//记录路线
	struct Move* Next;						//后向结点指针
};

Move *save = new Move;						//记录最短的移动方式
int min_move_steps=1000000;				    //记录最短移动步数
int first = 0;								//记录输出第几条路线
int second = 0;								//记录输出第几条最优路线

int n1;			//传教士人数
int n2;			//野人人数
int c;			//船的最大容量

void nextStep(int i); /*下一步,递归*/
void showOptimal(); /*输出最优路线*/
void showSolution(int num); /*打印所有路线*/
void Empty(Move *point); /*清空指针链表*/
int Islegal(int num); /*判断当前状态是否越界或不安全*/

int main()
{
     
	//初始化状态
	printf("Please input n : ");
	scanf_s("%d", &n1);
	n2 = n1;
	printf("Please input c : ");
	scanf_s("%d", &c);

	save->Next = NULL;

	States[0].mLeft = n1;
	States[0].cLeft = n2;
	States[0].mRight = 0;
	States[0].cRight = 0;
	States[0].b = 1;

	int a = Islegal(0);		//判断输入是否合法
	if (a)
	{
     
		printf("%s \n", "All Procedure:");
		nextStep(0);
	}
	else printf("输入不合法 \n");

	printf("%s \n", "Optimal Procedure:");
	showOptimal();
	
}

/*判断当前状态是否越界或不安全*/
int Islegal(int num)
{
     
	if ((States[num].cLeft > n1) || (States[num].cLeft < 0))
		return 0;

	if ((States[num].mLeft > n2) || (States[num].mLeft < 0))
		return 0;

	if ((States[num].mLeft != 0) && (States[num].cLeft > States[num].mLeft))
		return 0;

	if ((States[num].cRight > n1) || (States[num].cRight < 0))
		return 0;

	if ((States[num].mRight > n2) || (States[num].mRight < 0))
		return 0;

	if ((States[num].mRight != 0) && (States[num].cRight > States[num].mRight))
		return 0;

	return 1;
}

/*清空指针链表*/
void Empty(Move *point)
{
     
	if (point->Next)
		Empty(point->Next);
	if (point == save)
	{
     
		strcpy_s(point->ch, "");
		point->Next = NULL;
		return;
	}
	delete point;
}

/*打印路线*/
void showSolution(int num)
{
     
	int c;
	
	
	if (num < min_move_steps || num == min_move_steps)
	{
     
		if (num < min_move_steps)
		{
     
			Empty(save);
			min_move_steps = num;
		}
		Move *point = save;
		while (point->Next!=NULL)
		{
     
			point = point->Next;
		}
		for (c = 0; c < num; c++)
		{
     
			char ccc[6];
			ccc[0] = 48 + States[c].mLeft;
			ccc[1] = 48 + States[c].cLeft;
			ccc[2] = 48 + States[c].b;
			ccc[3] = '-';
			ccc[4] = '>';
			ccc[5] = '\0';
			strcat_s(point->ch, ccc);
		}
		strcat_s(point->ch, "000");
		//printf("%s", point->ch);
		Move *point_2 = new Move;
		point_2->Next = NULL;
		point->Next = point_2;
	}
	printf("No.%d Route:", ++first);
	for (c = 0; c < num ; c++)
	{
     
		printf("%d%d%d", States[c].mLeft, States[c].cLeft, States[c].b);
		printf("->");
	}
	printf("%d%d%d\n", States[num].mLeft, States[num].cLeft, States[num].b);
}

/*输出最优路线*/
void showOptimal()
{
     
	Move *point = save;
	if(!point->Next)
	{
     
		printf("%s \n", "Successed or Failed ? : Failed");
		return;
	}
	while(point->Next)
	{
     
		printf("No.%d Optimal Route:", ++second);
		printf("%s\n", point->ch);
		point = point->Next;
	}
	printf("%s \n", "Successed or Failed ? : Successed");
}

/*下一步,递归*/
void nextStep(int i)
{
     
	if (i >= 150)
	{
     
		printf("%s  \n", "steplength reached 150,have problem ");
		return;
	}

	for (int k = 0; k < i; k++) /*if the current state is same to previous,retrospect*/   //如果有出现重复走法的,那么退出(通过哈希值进行比较)
		if (States[i].mLeft == States[k].mLeft && States[i].cLeft == States[k].cLeft && States[i].b == States[k].b)
			return;

	if ((States[i].mLeft == 0) && (States[i].cLeft == 0) && (States[i].b == 0))
	{
     
		showSolution(i);
		return;
	}

	int next = i + 1;

	for (int k = 0; k <= c; k++)
		for (int j = 0; j <= c - k; j++)
		{
     
			if (k == 0 && j == 0)continue;

			if (States[i].b == 1)									//由父结点进行状态转移,生成子结点
			{
     
				States[next].mLeft = States[i].mLeft - k;
				States[next].cLeft = States[i].cLeft - j;
				States[next].mRight = States[i].mRight + k;
				States[next].cRight = States[i].cRight + j;
				States[next].b = 0;
			}
			else
			{
     
				States[next].mLeft = States[i].mLeft + k;
				States[next].cLeft = States[i].cLeft + j;
				States[next].mRight = States[i].mRight - k;
				States[next].cRight = States[i].cRight - j;
				States[next].b = 1;
			}

			if (Islegal(next) == true)								//子结点不可行,则删除之
			{
     
				nextStep(next);
				continue;
			}
		}
}

六、实验结果及分析

(1)对于猴子摘香蕉问题根据自己编写代码或者参考代码,用不同的初始状态测试代码,记录每种初始状态下的输出结果,并对每个结果进行解释。
编写代码思路:使用DFS、递归遍历所有可能的情况并打印出来。香蕉对于猴子来说,不知道在哪里,所以对于猴子来说,要把下一步可能的情况都试一下,因为下一步有多种且是有限确定的走法,借助递归算法做出当前位置的判断,给出下一步的走法即可。
① 猴子在A,箱子在C,香蕉在B,不站在箱子上

结果分析:可以看到有4种路径都可以顺利摘到香蕉,最优的方法是第3种,只需4步。

② 猴子在C,箱子在C,香蕉在A,不站在箱子上
广州大学人工智能原理实验一:知识的表示与推理实验_第2张图片

结果分析:可以看到有2种路径都可以顺利摘到香蕉,最优的方法是第2种,只需3步。

③ 猴子在B,箱子在B,香蕉在C,站在箱子上
广州大学人工智能原理实验一:知识的表示与推理实验_第3张图片

结果分析:可以看到有2种路径都可以顺利摘到香蕉,最优的方法是第2种,只需4步。要先跳下箱子,再推动箱子,爬上去摘到香蕉。

④ 猴子在C,箱子在B,香蕉在A,站在箱子上
在这里插入图片描述

结果分析:猴子和箱子不在同一个位置上,不可能站在箱子上,所以这种初始状态不正确。

(2)完善猴子摘香蕉问题参考代码,参考代码中有什么问题?应该如何修改会更好。
① 递归使用错误,通过下图,可以看到代码出现了一些执行两步后,再调用自身,这种情况会导致第一次打印正确,但后面结果会打印错误的路线。这种情况应该直接修改nextstep()函数内的代码,使得递归正常。

② 该代码只考虑了香蕉在B的情况。在判断到达结束时,该代码给出了States[i].banana == 0限制了香蕉的位置,我们可以通过修改0为我们输入的位置,来获得更加多可能输入的情况。
③ 对于某些输入,打印结果错误。我们发现猴子在爬上箱子后,会直接获得香蕉,这种情况显然是不正确的,所以可以把获得香蕉这一步骤移至代码中的判断到达终点这一步骤,当猴子和箱子和香蕉在同一位置上,并且猴子在箱子上时,可获得香蕉。
④ 对于以上错误,我的代码均以全部纠正,并且完善了原代码的一些冗余地方。
(3)传教士(牧师)与野人问题,写出状态表示的数据结构,还有每种解的状态迁移图示。
typedef struct State //描述问题状态
{
int mLeft; //左岸传教士数量
int cLeft; //左岸野人数量
int mRight; //右岸传教士数量
int cRight; //右岸野人数量
int b; //船在左岸还是右岸
};

typedef struct Move //描述最优路线
{
char ch[1000]=""; //记录路线
struct Move* Next; //后向结点指针
};

两个传教士(牧师)与两个野人,小船一次最多载两人状态迁移图示
广州大学人工智能原理实验一:知识的表示与推理实验_第4张图片

(4)尝试写出传教士(牧师)与野人问题的程序清单。
void nextStep(int i); /下一步,递归/
void showOptimal(); /输出最优路线/
void showSolution(int num); /打印所有路线/
void Empty(Move *point); /清空指针链表/
int Islegal(int num); /判断当前状态是否越界或不安全/

全部代码见第五部分——实验源代码
(5)实验结果讨论。
两个传教士(牧师)与两个野人,小船一次最多载两人
广州大学人工智能原理实验一:知识的表示与推理实验_第5张图片

三个传教士(牧师)与三个野人,小船一次最多载三人


广州大学人工智能原理实验一:知识的表示与推理实验_第6张图片

由于题目要求输出所有可能的路线,并输出最优的路线,所以使用DFS的递归实现无疑是最简单的实现算法,如果只要求最快地输出一种尽可能快的路线,我们可以使用BFS(最佳优先搜索)算法加快效率找到局部最优解。根据f(n)=h(n),h(n)选择左岸上牧师与野人的总数量可以尽可能快的找到局部最优解。

注:也可以修改估价函数变成实现A*算法,使f(n)=g(n)+h(n),保证效率的前提下,寻得全局最优解。

七、最佳优先搜索BFS求解传教士(牧师)与野人问题

BFS(最佳优先搜索)效果如下:
在这里插入图片描述

使用的估价函数:

int Heruistic(STATUS *Node) //估价函数,左岸上牧师与野人数量越少,评价越高
{
     
	return (Node->mLeft + Node->cLeft);
}

BFS(最佳优先搜索)代码如下:

#include 
#include 

typedef struct status						//描述问题状态
{
     
	int mLeft;								//左岸传教士数量
	int cLeft;								//左岸野人数量
	int mRight;								//右岸传教士数量
	int cRight;								//右岸野人数量
	int b;									//船在左岸还是右岸
	int Hash;								//为了快速判断结点是否存在引入的哈希值
	int HVal;								//启发函数值
	struct status *Next;					//后向结点指针
	struct status *Prev;					//前驱结点指针
} STATUS;


STATUS *OpenHead = new STATUS;				//创建Open表,保存所有已生成而未考察的结点
STATUS *CloseHead = NULL;					//创建Close表,暂为空,保存所有已考察的结点

int Hash(STATUS *Node);						//哈希函数
int Heruistic(STATUS *Node);				//启发函数

bool isTarget(STATUS *Current);				//判断是否满足目标状态
bool isSafe(STATUS *Node);					//判断给定状态是否安全

STATUS* AStar(STATUS *Current);				//A*算法主函数
void ResultOut(STATUS *Current);			//输出结果

STATUS* GetLastNodeOpen();					//取Open表最后一个节点
STATUS* GetLastNodeClose();					//取Close表最后一个节点
void RemoveOpen(STATUS *Node);				//从Open表中删除节点
void RemoveClose(STATUS *Node);				//从Close表中删除节点
STATUS* isInOpen(STATUS *Node);				//判断指定结点是否在Open表中
STATUS* isInClose(STATUS *Node);			//判断指定结点是否在Close表中
void InsertOpen(STATUS *Node);				//插入节点到Open表中
void InsertClose(STATUS *Node);				//插入节点到Close表中
void SwapOpen();							//对Open表按启发函数排序

int n1 = 2;			//传教士人数
int n2 = 2;			//野人人数
int c = 2;			//船的最大容量

int main()
{
     
	printf("Please input n : ");
	scanf_s("%d", &n1);
	n2 = n1;
	printf("Please input c : ");
	scanf_s("%d", &c);

	STATUS *Current = NULL;

	OpenHead->mLeft = n1;									//创建初始状态,得到最初的父节点
	OpenHead->cLeft = n2;
	OpenHead->mRight = 0;
	OpenHead->cRight = 0;
	OpenHead->b = 1;
	OpenHead->Hash = Hash(OpenHead);
	OpenHead->HVal = Heruistic(OpenHead);
	OpenHead->Next = NULL;
	OpenHead->Prev = NULL;

	while (OpenHead)											//Open表非空
	{
     
		Current = GetLastNodeOpen();							//从Open表中得到启发函数最小的结点作为父节点,并从Open表中删除
		RemoveOpen(Current);

		if (isTarget(Current))									//达到目标状态则输出结果,否则继续A*算法
		{
     
			ResultOut(Current);
			//getchar();
			return 0;
		}
		else
		{
     
			AStar(Current);
		}
	}
	printf("Successed or Failed?: Failed");
}

void ResultOut(STATUS *Current)
{
     
	Current->Next = NULL;

	do   //回溯法
	{
     
		Current->Prev->Next = Current;
		Current = Current->Prev;
	} while (Current->Prev != NULL);

	int i = 0;

	printf("Optimal Procedure :");
	while (Current != NULL)
	{
     
		printf("%d%d%d", Current->mLeft, Current->cLeft, Current->b);


		Current = Current->Next;

		if (Current == NULL)break;
		printf("->");
	}
	printf("\nSuccessed or Failed?: Successed\n");

}

STATUS* AStar(STATUS *Current)									//实现A*算法
{
     

	for (int i = 0; i <= c; i++)
		for (int j = 0; j <= c - i; j++)
		{
     
			if (i == 0 && j == 0)continue;
			STATUS* Child = new STATUS;
			STATUS* NodeTmp;

			if (Current->b == 1)									//由父结点进行状态转移,生成子结点
			{
     
				Child->mLeft = Current->mLeft - i;
				Child->cLeft = Current->cLeft - j;
				Child->mRight = Current->mRight + i;
				Child->cRight = Current->cRight + j;
				Child->b = 0;
				Child->Hash = Hash(Child);
				Child->HVal = Heruistic(Child);
				Child->Next = NULL;
				Child->Prev = Current;
			}
			else
			{
     
				Child->mLeft = Current->mLeft + i;
				Child->cLeft = Current->cLeft + j;
				Child->mRight = Current->mRight - i;
				Child->cRight = Current->cRight - j;
				Child->b = 1;
				Child->Hash = Hash(Child);
				Child->HVal = Heruistic(Child);
				Child->Next = NULL;
				Child->Prev = Current;
			}

			if (isSafe(Child) == true)								//子结点不可行,则删除之
			{
     
				delete Child;
				Child = NULL;
				continue;
			}

			if (((NodeTmp = isInOpen(Child)) == NULL) && \
				((NodeTmp = isInClose(Child)) == NULL))			//若子结点不在Open表和Close表中
			{
     
				//Child->HVal = Heruistic(Child);						//求子结点的启发函数,这里多余了,前面已经求了,kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
				InsertOpen(Child);									//将子结点插入Open表中
			}
			else if ((NodeTmp = isInOpen(Child)) != NULL)			//若子结点在Open表中
			{
     
				if (Heruistic(Child) < Heruistic(NodeTmp))			//子结点的启发函数小于Open表中的启发函数
					NodeTmp->HVal = Heruistic(Child);				//更新OPEN表中的启发函数

				Child->Next = NULL;									//删除结点,防止内存泄漏
				delete Child;
				Child = NULL;
			}
			else													//子结点在CLOSE表中
			{
     
				NodeTmp = isInClose(Child);							//取得该结点

				if (Heruistic(Child) < Heruistic(NodeTmp))			//子结点的启发函数小于Close表中的启发函数
				{
     
					NodeTmp->HVal = Heruistic(Child);				//更新Close表中的启发函数值

					InsertOpen(NodeTmp);							//将此结点从CLOSE表中移出, 并放入OPEN表中
					RemoveClose(NodeTmp);
				}

				Child->Next = NULL;									//删除结点,防止内存泄漏
				delete Child;
				Child = NULL;
			}

		}

	InsertClose(Current);										//将父节点插入CLOSE表中;
	SwapOpen();													//根据启发函数重排OPEN表。这样循环中每一步只考虑OPEN表中状态最好的结点
	return NULL;
}

STATUS* isInOpen(STATUS *Node)									//使用哈希值查找指定的结点,下同
{
     
	STATUS* NodeTmp = OpenHead;

	if (OpenHead == NULL)
		return NULL;

	do
	{
     
		if (Hash(NodeTmp) == Hash(Node))
			return NodeTmp;
		else
			NodeTmp = NodeTmp->Next;
	} while (NodeTmp != NULL);

	return NULL;
}

STATUS* isInClose(STATUS *Node)
{
     
	STATUS* NodeTmp = CloseHead;

	if (CloseHead == NULL)
		return NULL;

	do
	{
     
		if (Hash(NodeTmp) == Hash(Node))
			return NodeTmp;
		else
			NodeTmp = NodeTmp->Next;
	} while (NodeTmp != NULL);

	return NULL;
}

bool isSafe(STATUS *Node)										//判断当前状态是否越界或不安全
{
     
	if ((Node->cLeft > n1) || (Node->cLeft < 0))
		return true;

	if ((Node->mLeft > n2) || (Node->mLeft < 0))
		return true;

	if ((Node->mLeft != 0) && (Node->cLeft > Node->mLeft))
		return true;

	if ((Node->cRight > n1) || (Node->cRight < 0))
		return true;

	if ((Node->mRight > n2) || (Node->mRight < 0))
		return true;

	if ((Node->mRight != 0) && (Node->cRight > Node->mRight))
		return true;

	return false;
}

int Hash(STATUS *Node)   //把结点转换成 左岸的牧师与野人与船位置的数,加快查找速度
{
     
	return ((Node->mLeft) << 8) | ((Node->cLeft) << 4) | (Node->b);
}

int Heruistic(STATUS *Node) //估价函数,左岸上牧师与野人数量越少,评价越高
{
     
	return (Node->mLeft + Node->cLeft);
}

bool isTarget(STATUS *Current)
{
     
	if ((Current->mLeft == 0) && (Current->cLeft == 0) && (Current->b == 0))
		return true;
	else
		return false;
}

void InsertOpen(STATUS *Node)									//直接插入到链表末尾,下同
{
     
	STATUS *NodeTmp;

	if (OpenHead == NULL)
	{
     
		OpenHead = Node;
		return;
	}
	else
	{
     
		NodeTmp = GetLastNodeOpen();
		NodeTmp->Next = Node;
		Node->Next = NULL;
	}

	return;
}

void InsertClose(STATUS *Node)
{
     
	STATUS *NodeTmp;

	if (CloseHead == NULL)
	{
     
		CloseHead = Node;
		return;
	}
	else
	{
     
		NodeTmp = GetLastNodeClose();
		NodeTmp->Next = Node;
		Node->Next = NULL;
	}
	return;
}

void RemoveOpen(STATUS *Node)
{
     
	STATUS *NodeTmp = NULL;
	STATUS *NodeTmp2 = NULL;

	NodeTmp = OpenHead;

	while (Hash(NodeTmp) != Hash(Node))				//使用哈希值查找指定的结点,下同
	{
     
		NodeTmp2 = NodeTmp;
		NodeTmp = NodeTmp->Next;
	}

	if (NodeTmp == OpenHead)
	{
     
		OpenHead = NodeTmp->Next;
		NodeTmp = OpenHead;
	}
	else
		NodeTmp2->Next = NodeTmp->Next;
}

void RemoveClose(STATUS *Node)
{
     
	STATUS *NodeTmp = NULL;
	STATUS *NodeTmp2 = NULL;

	NodeTmp = CloseHead;

	while (Hash(NodeTmp) != Hash(Node))
	{
     
		NodeTmp2 = NodeTmp;
		NodeTmp = NodeTmp->Next;
	}

	if (NodeTmp == CloseHead)
	{
     
		OpenHead = NodeTmp->Next;
		NodeTmp = CloseHead;
	}
	else
		NodeTmp2->Next = NodeTmp->Next;
}

STATUS* GetLastNodeOpen()										//遍历整个链表并返回最后一个元素,下同
{
     
	STATUS* NodeTmp = OpenHead;

	if (NodeTmp == NULL)
		return NULL;
	else
	{
     
		while (NodeTmp->Next != NULL)
		{
     
			NodeTmp = NodeTmp->Next;
		}

		return NodeTmp;
	}
}

STATUS* GetLastNodeClose()
{
     
	STATUS* NodeTmp = CloseHead;

	if (NodeTmp == NULL)
		return NULL;
	else
	{
     
		while (NodeTmp->Next != NULL)
		{
     
			NodeTmp = NodeTmp->Next;
		}

		return NodeTmp;
	}
}

void SwapOpen()													//从大到小,直接插入排序
{
     
	STATUS *NodeFirst = NULL;
	STATUS *NodeTmp = NULL;
	STATUS *Nodeq = NULL;
	STATUS *Nodep = NULL;

	if (OpenHead == NULL)
		return;

	NodeFirst = OpenHead->Next; 								//原链表剩下用于直接插入排序的节点链表
	OpenHead->Next = NULL; 										//只含有一个节点的链表的有序链表

	while (NodeFirst != NULL) 									//遍历剩下无序的链表
	{
     
		for (NodeTmp = NodeFirst, Nodeq = OpenHead; ((Nodeq != NULL) && (Nodeq->HVal > NodeTmp->HVal)); \
			Nodep = Nodeq, Nodeq = Nodeq->Next); 			//无序节点在有序链表中找插入的位置

	//找到了插入的位置则退出for循环
		NodeFirst = NodeFirst->Next; 							//无序链表中的节点离开,以便它插入到有序链表中

		if (Nodeq == OpenHead) 									//插在第一个节点之前
		{
     
			OpenHead = NodeTmp;
		}
		else 													//p是q的前驱
		{
     
			Nodep->Next = NodeTmp;
		}

		NodeTmp->Next = Nodeq; 									//完成插入动作
	}
}

你可能感兴趣的:(比赛+项目开源方案,链表,算法,数据结构,人工智能,图搜索算法)