A算法求解八数码问题(C++实现)

【实验原理】

1. 八数码问题
判断有无解问题:根据逆序数直接判断有无解,对于一个八数码,依次排列之后,每次是将空位和相邻位进行调换,研究后会发现,每次调换,逆序数增幅都为偶数,也就是不改变奇偶性,所以初始和目标状态的逆序数的奇偶性相同。

2. 状态图搜索
(1)搜索树:搜索过程中经过的节点和边按原图的连接关系构成一个树型的有向图,称为搜索树。
(2)搜索方式:树式搜索——记录搜索过程中所经过的所有节点和边
(3)路径的获得:树式搜索——反向求解
(4)CLOSED表和OPEN表:CLOSED表对树式搜索来说存储的是正在成长的搜索树,对线式搜索来说存储的是不断伸长的折线,本身就是所求的路径。OPEN表存储当前待考查的节点。

3. A算法
(1)启发函数:用来估计搜索树上节点X与目标节点Sg接近程度的函数,记为h(x).
(2)估价函数: f ( x ) = g ( x ) + h ( x ) f(x)=g(x)+h(x) f(x)=g(x)+h(x); 其中 g ( x ) g(x) g(x)是代价函数, h ( x ) h(x) h(x)是启发函数。 或定义为: f ( x ) = d ( x ) + h ( x ) f(x)=d(x)+h(x) f(x)=d(x)+h(x); d ( x ) d(x) d(x) x x x的深度

(3)算法步骤
S t e p 1 Step1 Step1:将S0加入OPEN表
S t e p 2 Step2 Step2:若OPEN表为空,则搜索失败,退出
S t e p 3 Step3 Step3:从OPEN表中将第一个结点N移入CLOSED表中
S t e p 4 Step4 Step4:如果N是目标结点,则搜索成功
S t e p 5 Step5 Step5:若不可扩展,转 S t e p 2 Step2 Step2
S t e p 6 Step6 Step6:对扩展的字结点做相应处理:
①生成子结点中是否有OPEN表或CLOSED表中已存在的点;若有则再考虑其中有无N的先辈结点,有则删除;对于其余结点也删除之,修改其f(x)值;
②修改父节点指针,其余子节点加入OPEN中,OPEN表排序,转 S t e p 2 Step2 Step2

【实验内容】

1. 要求

(1) 定义代价函数 G ( x ) G(x) G(x)和启发函数 H ( X ) H(X) H(X),以A算法进行求解。
(2) 输入初始状态和目标状态。
(3) 输出从初始状态到目标状态的路线。

2. 代码清单
#include <iostream>
#include <type_traits>
#include<stdlib.h>
#include <windows.h>
#include <time.h>
using namespace std;
#define MAXNUM 100
#define OK 1
#define ERROR 0
int ss0[9] = { 0 };  // 初始结点,待输入
int ssg[9] = { 0 };  // 目标节点,待输入
class Table; 

class Node
{  // 节点类
public:
	Node();  // 默认构造函数
	Node(int s[]);  // 用数组初始化的构造函数
	Node &operator=(const Node ss); //运算符重载,用于赋值
	void showStatus();	// 显示结点状态
	bool operator==(Node &N); // 运算符重载,用于判断是否相等
	int Ancestors(Node ss);			// 先辈结点
	int gx();		// 代价函数
	int hx();		// 启发函数
	int fx();		// 估价函数
	int expand( Node kid[]);	// 扩展子节点
	friend class Table;				// 友元类Table
	friend int A(Node S0, Node Sg);   // 友元函数A算法

private:
	int *status = new int[9]; // 结点状态
	int steps;  // 节点深度
	int f;		// 结点fx值 ֵ
	Node *father; // ָ父节点指针
};
Node deleted;	// 表示被删除的结点,用于比较与赋值
Node::Node()	// 默认构造函数
{ 
	for (int i = 0; i < 9; i++) {
		status[i] = 0;
	}
	f = steps = 0;
	father = NULL;
}
Node::Node(int s[])// 用数组初始化的构造函数
{ 
	for (int i = 0; i < 9; i++) {
		status[i] = s[i];
	}
	steps = 0;
	f = fx();
	father = NULL;
}
Node& Node::operator=(const Node ss)//运算符重载,用于赋值
{ 
	for (int i = 0; i < 9; i++) {
		status[i] = ss.status[i];
	}
	steps = ss.steps;
	f = ss.f;
	father = ss.father;
	return *this;
}
void Node::showStatus()// 显示结点状态
{ 
	cout << "┏━━━━━━━━━━┓" << endl;
	for (int i = 0; i < 3; i++) { 
		cout << "┃";
		for (int j = 0; j < 3; j++) { 
			if (status[i * 3 + j] == 0) {
				cout << "   ";
			}
			else {
				cout << "  " << status[i * 3 + j];
			}
		}
		cout << " ┃" << endl;
	}
	cout << "┗━━━━━━━━━━┛" << endl;
}
bool Node::operator==(Node &N)
{ 
	for (int i = 0; i < 9; i++) { 
		if (status[i] != N.status[i]) {
			return false;
		}
	}
	return true;
}
int Node::Ancestors(Node ss)	// 查找ss是否为先辈结点,是则返回高的辈数
{ 
	int generation = 1; 
	Node *p = father;
	while (p != NULL) {
		if (*p == ss) {
			return generation;
		}
		p = p->father;
		generation++;
	}
	return 0;
}
int Node::gx()
{
	return steps;// 代价函数值即结点深度
}
int Node::hx()
{ 
	int h = 0;
	for (int i = 0; i < 9; i++) {
		if (status[i] != ssg[i]) {
			h++;	// 每有一位与目标节点不一致,启发函数值加一
		}
	}
	return h;
}
int Node::fx()
{
	return (gx() + hx());
}
int Node::expand(Node kid[])
{
	int point, i, j;
	for (i = 0; i < 9; i++) {
		if (status[i] == 0) {
			point = i;
		}
	}
	i = 0;
	if (point - 3 >= 0) { // 防止向上扩展越界
		for (j = 0; j < 9; j++) {
			kid[i].status[j] = status[j];
		}
		swap(kid[i].status[point], kid[i].status[point - 3]);
		kid[i].steps = steps + 1;
		kid[i].f = kid[i].fx();
		i++;
	}
	if (point + 3 <= 8) { // 防止向下扩展越界
		for (j = 0; j < 9; j++) {
			kid[i].status[j] = status[j];
		}
		swap(kid[i].status[point], kid[i].status[point + 3]);
		kid[i].steps = steps + 1;
		kid[i].f = kid[i].fx();
		i++;
	}
	if (point % 3 != 0) { // 防止向左扩展越界
		for (j = 0; j < 9; j++) {
			kid[i].status[j] = status[j];
		}
		swap(kid[i].status[point], kid[i].status[point - 1]);
		kid[i].steps = steps + 1;
		kid[i].f = kid[i].fx();
		i++;
	}
	if ((point + 1) % 3 != 0) { // 防止向右扩展越界
		for (j = 0; j < 9; j++) {
			kid[i].status[j] = status[j];
		}
		swap(kid[i].status[point], kid[i].status[point + 1]);
		kid[i].steps = steps + 1;
		kid[i].f = kid[i].fx();
		i++;
	}
	if (i == 0) {
		return ERROR;
	}
	else {
		return i; 
	}
}

class Table
{ 
public:
	Table();  // 构造函数
	int Add(Node S);   // 向表中添加结点
	int Delete(int i);     // 删除节点
	bool isEmpty();      // 判断表空
	int Search(Node NS);    // 在表中查找NS结点
	int Sort();				// 将表按fx升序排序
	void showNode();	// 打印表
	friend int A(Node S0, Node Sg); // A算法

private:
	Node node[MAXNUM];  // 表中结点
	int length;   // 表长
};
Table::Table()
{// 构造函数
	for (int i = 0; i < MAXNUM; i++) { // 初始化表长为0
		length = 0;
	}
}
int Table::Add(Node S)
{
	if (length < MAXNUM) { // 判断是否已满
		node[length] = S;
		length++;
		return OK;
	}
	else {
		return ERROR;
	}
}
int Table::Delete(int i)
{ 
	if (i > 0 && i < length) {  // 判断是否表空
		for (int j = i - 1; j < length - 1; j++) {
			node[j] = node[j + 1];
		}
		length--;
		return OK;
	}
	else if (i == length)
	{
		node[i - 1]=deleted;
		length--;
		return OK;
	}
	else {	// 错误提示
		cout << "Input illegal in delete function!" << endl;
		return ERROR;
	}
}
bool Table::isEmpty()
{
	if (length > 0) { // 非空
		return false;
	}
	else {// 为空
		return true;
	}
}
int Table::Search(Node NS)
{
	for (int i = 0; i < length; i++) {
		if (NS == node[i]) {
			return i + 1; // 返回NS在表中序号
		}
	}
	return 0;
}
int Table::Sort()
{ // 按fx升序排序
	if (isEmpty()) {
		return ERROR;
	}
	else {
		Node temp;
		for (int i = 0; i < length - 1; i++) {
			for (int j = 0; j < length - 1 - i; j++) {
				if (node[j].f > node[j + 1].f) {
					temp = node[j];
					node[j]=node[j + 1];
					node[j + 1] = temp;
				}
			}
		}
		return OK;
	}
}
void Table::showNode()
{ // 打印表
	if (length == 0) {
		cout << "Empty!" << endl;
	}
	else {
		for (int i = 0; i < length - 1; i++)
		{
			node[i].showStatus();
			cout << "     ↓     " << endl;
		}
		node[length - 1].showStatus();
	}
}
Table Path; // 记录求解路径

int inversions(int S[])	// 计算s数组的逆序数
{
	int i = 0, point;
	int ss[8] = { 0 };
	int s_inversions = 0;	//逆序数
	for (i = 0; i < 9; i++) {
		if (S[i]==0){
			point = S[i];
		}
		else {
			ss[i] = S[i];
		}
	}
	for (i = point; i < 9; i++) {
		ss[point] = S[point + 1];
	}
	for (i = 0; i < 8; i++) {
		for (int j = i; j < 8; j++)
		{
			if (ss[i] > ss[j])
			s_inversions++;
		}
	}
	return s_inversions;
}
int A(Node S0, Node Sg)
{	// A算法
	if ((inversions(S0.status) + inversions(Sg.status)) % 2 != 0) {
		return ERROR;
	}
	Table OPEN, CLOSED;		// OPEN和CLOSED表
	int &m = OPEN.length;		// OPEN表长别名
	int &n = CLOSED.length;	// CLOSED表长别名
	int time = 0;						
	Node *kid = new Node[4];
	
	cout << "Original status: " << endl; 
	S0.showStatus();
	cout << "Goal status: " << endl;
	Sg.showStatus();
	cout << endl << "========================" << endl;
	OPEN.Add(S0);					//  Step1:将S0加入OPEN表
	while (true) {
		if (OPEN.isEmpty()) {		// Step2:若OPEN表为空,则搜索失败,退出
			return ERROR;
		}
		CLOSED.Add(OPEN.node[0]);	// Step3:从OPEN表中将第一个结点N移入CLOSED表中
		Node &N = CLOSED.node[n - 1];	// 
		OPEN.Delete(1);

		if (N==Sg) {		// Step4:如果N是目标结点,则搜索成功
			cout << "Find succeed!" << endl;
			cout <<  "========================" << endl;
			system("pause");
			return OK;
		}
		int kid_num = N.expand(kid);
		int flag = 4;
		for (int i = 0; i < kid_num; i++) {
			if ((OPEN.Search(kid[i]) != 0) || (CLOSED.Search(kid[i]) != 0))
			{
				flag--;	
			}
		}
		if (flag == ERROR) { // Step5:若不可扩展,转Step2
			continue;
		}
		for (int i = 0; i < kid_num; i++) { //Step6:对扩展的字结点做相应处理
			int exOPEN = OPEN.Search(kid[i]);// 生成子结点中是否有OPEN表中已存在的点
			int exCLOSED = CLOSED.Search(kid[i]);// 生成子结点中是否有CLOSED表中已存在的点
			if (exOPEN > 0) {  
				if (N.Ancestors(kid[i])) {// 若有则再考虑其中有无N的先辈结点,有则删除
					kid[i] = deleted;
				}
				else {  // 对于其余结点也删除之,修改其f(x)值
					if (OPEN.node[exOPEN - 1].f > kid[i].f) {
						OPEN.node[exOPEN - 1].father = &N;
						OPEN.node[exOPEN - 1].f = kid[i].f;
					}
					kid[i] = deleted;
				}
			}	// if exOPEN>0
			else if (exCLOSED>0) {
				if (N.Ancestors(kid[i])!=0) {// 若有则再考虑其中有无N的先辈结点,有则删除
					kid[i] = deleted;
				}
				else {  // 对于其余结点也删除之,修改其f(x)值
					if (CLOSED.node[exCLOSED - 1].f > kid[i].f) {
						CLOSED.node[exCLOSED - 1].father = &N;
						CLOSED.node[exCLOSED - 1].f = kid[i].f;
					}
					kid[i] = deleted;
				}
			}	// else if
			else {
				kid[i].father = &N; // 修改父节点指针
			}
			if (!(kid[i] == deleted))
			{
				OPEN.Add(kid[i]);	// 其余子节点加入OPEN中
			}
		}
		OPEN.Sort();  //OPEN表排序
		if ((Path.node[Path.length - 1] == *OPEN.node[0].father)&&!OPEN.isEmpty()) {
			Path.Add(OPEN.node[0]);
		}
		cout << "==========open============" << endl;
		OPEN.showNode();
		cout << endl;
		cout << "==========closed==========" << endl;
		CLOSED.showNode();
		cout << endl;
		cout << "==========path============" << endl;
		Path.showNode();
		cout << endl;
		system("pause");
		system("cls");
	}// while
	delete kid;
	return ERROR;
}

int main()
{
	cout << "Input the original status: ";	// 输入初始状态
	for (int i = 0; i < 9; i++) {
		cin >> ss0[i];
	}
	Node S0(ss0);
	Path.Add(S0);
	cout << "Input the goal status:     ";// 输入目标状态
	for (int i = 0; i < 9; i++) {
		cin >> ssg[i];
	}
	Node Sg(ssg);
	cout << endl;
	if (A(S0, Sg) == OK) {	//搜索成功
		cout << endl << endl;
		cout << "The find process is: " << endl;
		Path.showNode();// 打印求解路径
	}
	else {	// 提示搜索失败
		cout << "Search failed! This status have no solution!" << endl;
	}
	system("pause");
	return 0;
}

3. 运行结果

(1) 手动输入初始状态和目标状态
在这里插入图片描述
(2) 显示OPEN和CLOSED表
在这里插入图片描述
(3) 按下回车刷新界面,第二次子节点扩展:
在这里插入图片描述
(4) 搜寻成功,打印搜寻路径
在这里插入图片描述

【小结或讨论】

本次实验中,我直接按照课本(人工智能技术导论(第三版) 十一五
廉师友 / 2018-11 / 西安电子科技大学出版社)上的流程编写程序,Step6中,先判断在生成子结点中是否有OPEN表或CLOSED表中已存在的点;若有则再考虑其中有无N的先辈结点,有则删除;对于其余结点也删除之,修改其f(x)值,在这里我删除的是生成的子节点二并对CLOSED表进行修改,CLOSED表中并无重复结点,但由于未将这些节点加入OPEN表中重新扩充,导致在步数较多的情况下依然发生死循环,此外,与某位同学讨论得知,设置巧妙的启发函数可以提高查找效率,此处依然可以修改。

  • 8
    点赞
  • 83
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值