隐式图的搜索问题(解法二:C++代码实现)

一、实验任务

1、对九宫重排问题,建立图的启发式搜索求解方法。

2、用A*算法求解九宫重排问题。

二、实验要求

3х3九宫棋盘,放置数码为1~8的8个棋子,棋盘中留有一个空格,空格周围的棋子可以移动到空格中,从而改变棋盘的布局。根据给定初始布局和目标布局,移动棋子从初始布局到达目标布局,求解移动步骤并输出。请设计算法,使用合适的搜索策略,在较少的空间和时间代价下找到最短路径。

三、A*算法介绍

在这里插入图片描述

四、实践操作

A算法又称为启发式搜索算法。对启发式搜索算法,又可根据搜索过程中选择扩展节点的范围,将其分为全局择优搜索算法和局部择优搜索算法。

在全局择优搜索中,每当需要扩展节点时,总是从 Open 表的所有节点中选择一个估价函数值最小的节点进行扩展。其搜索过程可能描述如下:

把初始节点 S0 放入 Open 表中, f(S0)=g(S0)+h(S0) ;
如果 Open 表为空,则问题无解,失败退出;
把 Open 表的第一个节点取出放入 Closed 表,并记该节点为 n ;
考察节点 n 是否为目标节点。若是,则找到了问题的解,成功退出;
若节点 n 不可扩展,则转到第 (2) 步;
扩展节点 n ,生成子节点 ni ( i =1,2, …… ) ,计算每一个子节点的估价值 f( ni ) ( i =1,2, …… ) ,并为每一个子节点设置指向父节点的指针,然后将这些子节点放入 Open 表中;
根据各节点的估价函数值,对 Open 表中的全部节点按从小到大的顺序重新进行排序;
转第 (2) 步。
这里采用的启发式策略为:f(n) = g(n) + h(n),其中g(n)为从初始节点到当前节点的步数(层数),h(n)为 当前节点 “不在位 ”的方块数(也就是说不在位的方块数越少,那么临目标状态越近)例如下图中的h(n)=5,有的讲解的是不包含空格,我这里是包含了的,经测试只要前后标准一致,包不包含空格都一样。
g(n)为已经消耗的实际代价,即已经走了的步数。
h(n)为预测路径,即还有几个数字待走。

此时:h(n)=5
启发式策略工作扩展顺序如下:

在这里插入图片描述

五、C++代码实现

#include <iostream>
#include <time.h>
using namespace std;


/*
定义结构体
*/
struct EightDigital
{    //存储八数码
	int status[9];
	//存储走的是第几步(层数)
	int G;
	//存储不在位的格数(作为启发式函数)
	int H;
	//存储估价函数的值
	int F;
	//存储0数码的位置
	int Zero;
	//存储操作符(1左2右3上4下)
	int step;
	//父指针
	EightDigital* Parent;
};


#define MAXLISTSIZE 10000
#define MAXSTEPSIZE 100
//声明最终状态
int FinalStatus[9];
//定义OPEN表和CLOSE表,open和close是表中最后一个内容的下一位序号
EightDigital OPEN[MAXLISTSIZE];
EightDigital CLOSE[MAXLISTSIZE];
int open = 0;
int close = 0;

EightDigital* Node;






/*
计算不在位的字格数H
返回 H
*/
int CountH(int* status)
{
	int H = 0;
	int i;
	for (i = 0; i <= 8; i++)
	{
		if (FinalStatus[i] != status[i])
		{
			H++;
		}
	}
	return H;
}

/*
判断新生成的节点是否已经存在于OPEN表或CLOSE表中
返回 表征是否存在于OPEN或CLOSE的值,值为0 均不在,值>0 只在OPEN表,值<0 只在CLOSE表,|值|-1表示所在列表中的位置
*/
int Exist(EightDigital* N)
{
	int i, j;
	//计算不在位的字格数,如果为0,则证明给函数的节点在表中已存在
	int H = 0;
	int status[9];

	Node = new EightDigital;
	Node = N;

	for (i = 0; i <= 8; i++)
	{
		status[i] = Node->status[i];
	}
	//判断是否在OPEN表
	for (i = 0; i <= open - 1; i++)
	{
		for (j = 0; j <= 8; j++)
		{
			if (status[j] != OPEN[i].status[j])
			{
				H++;
			}
		}
		//H=0证明在表中找到该节点
		if (H == 0)
		{
			//如果在OPEN表中,返回i(节点在OPEN的位置)+ 1(在OPEN找到该节点)
			return i + 1;
		}
		//扫描完一个节点后重置H
		H = 0;
	}
	//判断是否在CLOSE表
	for (i = 0; i <= close - 1; i++)
	{
		for (j = 0; j <= 8; j++)
		{
			if (status[j] != CLOSE[i].status[j])
			{
				H++;
			}
		}
		//H=0证明在表中找到该节点
		if (H == 0)
		{
			//如果在CLOSE表中,返回-i(i为节点在CLOSE的位置)- 1(在CLOSE找到该节点)
			return (-i) - 1;
		}
		//扫描完一个节点后重置H
		H = 0;
	}

	return 0;
}

/*
初始化节点
返回 初始化后的节点Node
*/
EightDigital* EightDigitalInit(int status[10], int zero, int g, EightDigital* parent, int step)
{
	int i;
	Node = new EightDigital;
	for (i = 0; i <= 8; i++)
	{
		Node->status[i] = status[i];
	}
	Node->Zero = zero;
	Node->G = g;
	Node->H = CountH(Node->status);
	Node->F = Node->G + Node->H;
	Node->Parent = parent;
	Node->step = step;
	return Node;
}

/*
左移后的变化
返回 左移后的状态
*/
int* Left(int* s, int z)
{
	int temp, i;
	static int status[9];
	for (i = 0; i <= 8; i++)
	{
		status[i] = s[i];
	}
	//左移则是下标减1,需要与前一个位置进行值的交换
	temp = status[z - 1];
	status[z - 1] = 0;
	status[z] = temp;
	return status;
}

/*
右移后的变化
返回 右移后的状态
*/
int* Right(int* s, int z)
{
	int temp, i;
	static int status[9];
	for (i = 0; i <= 8; i++)
	{
		status[i] = s[i];
	}
	temp = status[z + 1];
	status[z + 1] = 0;
	status[z] = temp;
	return status;
}

/*
上移后的变化
返回 上移后的状态
*/
int* Up(int* s, int z)
{
	int temp, i;
	static int status[9];
	for (i = 0; i <= 8; i++)
	{
		status[i] = s[i];
	}
	temp = status[z - 3];
	status[z - 3] = 0;
	status[z] = temp;
	return status;
}

/*
下移后的变化
返回 下移后的状态
*/
int* Down(int* s, int z)
{
	int temp, i;
	static int status[9];
	for (i = 0; i <= 8; i++)
	{
		status[i] = s[i];
	}
	temp = status[z + 3];
	status[z + 3] = 0;
	status[z] = temp;
	return status;
}

/*
判断子节点是否在OPEN或CLOSE中,并进行对应的操作
返回值 NULL
*/
void ExistAndOperate(EightDigital* N)
{
	int i;
	//定义表示新生成节点是否在OPEN表或CLOSE表中, 值为0 均不在,值>0 只在OPEN表,值<0 只在CLOSE表
	int inList;
	Node = new EightDigital;
	Node = N;
	//如果是第一步的节点,直接加入OPEN中,返回
	if (Node->G == 1)
	{
		OPEN[open] = *Node;
		open++;
		return;
	}
	//判断新节点是否在OPEN或CLOSE中
	inList = Exist(Node);
	//如果均不在两个表中,将节点加入OPEN表中
	if (inList == 0)
	{
		//将拓展出的新结点加入到OPEN表中
		OPEN[open] = *Node;
		open++;
	}
	//如果在OPEN中,说明从初始节点到该节点找到了不同路径,保留耗散值短的那条路径
	else if (inList > 0)
	{//如果表内节点F值大于新节点F值,用新节点代替表内节点
		if (OPEN[inList - 1].F > Node->F)
		{
			OPEN[inList - 1] = *Node;
		}
	}
	//如果在CLOSE中,说明初始节点到该节点有两条路径,如果新找到的路径耗散值大,什么都不做,如果较小,将其从CLOSE中取出放入OPEN中    
	else if (inList < 0)
	{
		inList = -inList;
		//如果较小
		if (CLOSE[inList - 1].F > Node->F)
		{//将其取出放入OPEN
			OPEN[open] = *Node;
			open++;
		}
		//将其在CLOSE中释放
		for (i = inList - 1; i <= close - 1; i++)
		{
			CLOSE[i] = CLOSE[i + 1];
		}
		close--;
	}
}

/*
寻找最佳路径函数
返回 最后的节点Node
*/
EightDigital* Search()
{
	int* status;
	int i, j;

	EightDigital* Temp;
	//一直循环知道找到解结束
	while (1)
	{
		Temp = new EightDigital;
		//用冒泡排序给OPEN表里面的节点按耗散值进行排序
		for (i = open - 1; i > 0; i--)
		{
			for (j = 0; j < i; j++)
			{//从小到大进行排序
				if (OPEN[j].F > OPEN[j + 1].F)
				{//交换值
					*Temp = OPEN[j + 1];
					OPEN[j + 1] = OPEN[j];
					OPEN[j] = *Temp;
				}
			}
		}

		Node = new EightDigital;
		//从OPEN表中取出第一个元素(F值最小)
		*Node = OPEN[0];
		//判断该节点是否是目标节点,若是,则不在位的格数为0,算法结束,若不是,则将该结点进行扩展
		if (!CountH(Node->status))
		{
			break;
		}

		Temp = Node;
		//将扩展过的节点放入CLOSE 
		CLOSE[close] = *Node;
		close++;
		//将扩展的节点从OPEN中释放
		for (i = 0; i <= open - 1; i++)
		{//相当于是出栈
			OPEN[i] = OPEN[i + 1];
		}
		open--;
		//如果能左移,则进行左移创造新结点,下标为0,3,6则不能进行左移
		if ((Temp->Zero) % 3 >= 1)
		{//创造新结点
			Node = new EightDigital;
			//得到新的状态
			status = Left(Temp->status, Temp->Zero);
			//初始化新结点
			Node = EightDigitalInit(status, Temp->Zero - 1, (Temp->G) + 1, Temp, 1);
			//判断子节点是否在OPEN或CLOSE中,并进行对应的操作
			ExistAndOperate(Node);
		}
		//如果能右移,则进行右移创造新结点 ,下标为2,5,8则不能
		if ((Temp->Zero) % 3 <= 1)
		{  //创造新结点
			Node = new EightDigital;
			//得到新的状态
			status = Right(Temp->status, Temp->Zero);
			//初始化新结点
			Node = EightDigitalInit(status, Temp->Zero + 1, (Temp->G) + 1, Temp, 2);
			//判断子节点是否在OPEN或CLOSE中,并进行对应的操作
			ExistAndOperate(Node);
		}
		//如果能上移,则进行上移创造新结点  ,下标为0,1,2则不可以
		if (Temp->Zero >= 3)
		{
			Node = new EightDigital;
			//得到新的状态
			status = Up(Temp->status, Temp->Zero);
			//初始化新结点
			Node = EightDigitalInit(status, Temp->Zero - 3, (Temp->G) + 1, Temp, 3);
			//判断子节点是否在OPEN或CLOSE中,并进行对应的操作
			ExistAndOperate(Node);
		}
		//如果能下移,则进行下移创造新结点 ,下标为6,7,8则不可以
		if (Temp->Zero <= 5)
		{
			Node = new EightDigital;                                           //创造新结点
			status = Down(Temp->status, Temp->Zero);                   //得到新的状态
			Node = EightDigitalInit(status, Temp->Zero + 3, (Temp->G) + 1, Temp, 4);    //初始化新结点
			ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
		}
		//如果open=0, 证明算法失败, 没有解
		if (open == 0)
			return NULL;
	}
	return Node;
}

/*
展示具体步骤
返回 NULL
*/
void ShowStep(EightDigital* Node)
{
	int STEP[MAXSTEPSIZE];
	int STATUS[MAXSTEPSIZE][9];
	int step = 0;
	int i, j;
	int totalStep = Node->G;
	while (Node)
	{
		STEP[step] = Node->step;
		for (i = 0; i <= 8; i++)
		{
			STATUS[step][i] = Node->status[i];
		}
		step++;
		Node = Node->Parent;
	} 
	cout << "                               " << endl;
	cout << "****总步数为****:" << totalStep << endl;
	cout << "                               " << endl;
	for (i = step - 1; i >= 0; i--)
	{
		if (STEP[i] == 1)
			cout << "应向左移动一步" << endl;
		else if (STEP[i] == 2)
			cout << "应向右移动一步" << endl;
		else if (STEP[i] == 3)
			cout << "应向上移动一步" << endl;
		else if (STEP[i] == 4)
			cout << "应向下移动一步" << endl;
		else if (STEP[i] == 0)
			cout << "已启动:" << endl;
		for (j = 0; j <= 8; j++)
		{
			cout << STATUS[i][j] << " ";
			//换行输出
			if (j == 2 || j == 5 || j == 8)
				cout << endl;
		}
		cout << "                   " << endl;
	}
}
/*
主函数
*/
int main()
{
	int fstatus[9];
	int i, beginTime, endTime;
	EightDigital* FNode;
	EightDigital* EndNode;
	//输入初始状态
	cout << "请输入初始状态:" << endl;
	for (i = 0; i <= 8; i++)
	{
		cin >> fstatus[i];
	}
	cout << endl;
	//输入最终状态
	cout << "请输入目标状态:" << endl;
	for (i = 0; i <= 8; i++)
	{
		cin >> FinalStatus[i];
	}
	beginTime = clock();
	//判断0数码的位置
	for (i = 0; i <= 8; i++)
	{
		if (fstatus[i] == 0)
			break;
	}
	//获得初始节点
	FNode = EightDigitalInit(fstatus, i, 0, NULL, 0);
	//将初始节点放入OPEN中
	OPEN[open] = *FNode;
	open++;
	//寻找最佳路径
	EndNode = Search();

	if (!EndNode)
		cout << "抱歉,该目标无法实现!" << endl;
	else
		ShowStep(EndNode);                      //展示步骤

	endTime = clock();
	cout << "本次运行:" << endTime - beginTime << "ms" << endl;

	return 0;
}


六、结果展示

在这里插入图片描述
在这里插入图片描述

七、附录

1.参考文献

<1>A算法的详解一
https://www.sogou.com/link?url=hedJjaC291OB0PrGj_c3jPqV8iJQiL4Wim8oR1fyrtNWiXnyK1n-GVFro1BrEKsSHcpPnSGgxJEVuzaE0zfm9A…
<2>A
算法的详解二
https://zhuanlan.zhihu.com/p/45663817

2.解法一(Java代码实现)链接

跳转到Java代码实现

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值