人工智能大作业----八数码问题

人工智能大作业----八数码问题

原文指路
(版权声明:本文为CSDN博主「Lwhere~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Lwhere_/article/details/103068116)

原文文章已经写的很全了,但是可能有些地方不适用于VS新的版本,我做了些许修改,以下代码适用于VS2015。

做实验的各位希望能进原文好好看看再来粘代码。不然根本不知道是怎么做的。

修改处:(1)在run()函数里判断哪个节点更优的时候,将判断值F改为了g,因为相同节点的H(评估函数值)肯定是一样的;
(2)给run()函数加了一个参数H,表示使用的评估算法,这个H在main主函数里修改。(或者你也可以改为H需要用户输入。)
(3)读取节点依然使用freopen重定向,但之后为了运行的时候黑窗不闪退,又重定向了回来。
(4)我建立的是win32工作区,当时没有选择空项目,所以自动带了“stadfx.h”“targetver.h”,这两个头文件我不会放在下面,因为没做修改;创建的是空项目的同学把每个文件里的#include"stadfx.h"删了就行,不然会出错!
(5)给每个头文件加了防重定义的宏指令。
(6)把原文的board变量改名为了arc变量。
(7)改变了一下黑窗颜色。

代码:
1.function.h

#pragma once
#ifndef _FUNCTION_H_
#define _FUNCTION_H_
#include"global.h"
#include"stdafx.h"

bool success(const int &e, const int&s);             //检查是否到达目标路径
bool checkvalid(const int& cur, const int& tar); //检查是否有解
int H1(int curState, int tarState);                         //启发函数1
int H2(int curState, int tarState);                         //启发函数2
int H3(int curState);                                             //启发函数3
int H4(int curState, int tarState);                          //启发函数4
int F(const Snode &e);                                         //返回状态的F值
int arc_toState(const arc &se);                             //二维数组转换为一维数字
void state_toArc(int e, arc se);                             //一维数字转换为二维数组
void inPut(arc s);                                                  //输入初始序列和目标序列
void outPut(arc tmp);                                           //输出状态的state值的矩阵形式
void findPath(int pre, int size, int step);               //找到最优解的路径
int run(const int&st, const int &ed,int H);                     //运行A*函数算法主体
void goPath(int step);                                               //当算法运行完毕,检查


#endif // !_FUNCTION_H_

2.global.h

#pragma once
#ifndef _GLOBAL_H_
#define _GLOBAL_H_

#include"stdafx.h"
#include
#include  // 存储open表的数据结构
#include  // 存储closed表的数据结构
using namespace std;

typedef int arc[4][4];//将二维数组的定义重定义为arc

struct Snode {
	int state;//一维数字表示状态序列
	int g;    //路径深度
	int h;    //启发函数的评估值
	int cur;  //状态的序号标号
	int pre;  //状态的父节点标号

	Snode(int state, int g, int h, int cur, int pre) :state(state), g(g), h(h), cur(cur), pre(pre) {}

	bool operator <(const Snode&e)const {
		return(state < e.state);
	}
	bool operator ==(const Snode&e)const {
		return (state == e.state);  //即state值相同
	}
};

static int move_x[] = { -1,0,1,0 };  //左移动是-1,右移动是+1
static int move_y[] = { 0,1,0,-1 };  //下移动是1,上移动是-1
static multimap<int, Snode> open_key;//open表
static map<Snode, int> open_value;   //open表的value-key反表,便于查找元素
static map <int, bool> closed;       //closed表
static vector <Snode> path;          //保存closed表的具体结构路径


#endif // !_GLOBAL_H_

3.function.cpp

#include"stdafx.h"
#include"function.h"

//一维数字转换为二维数组
void state_toArc(int e, arc se)
{
	for (int i = 3; i >= 1; i--)
		for (int j = 3; j >= 1; j--)
		{
			se[i][j] = e % 10;
			e /= 10;
		}
}
//将二维数组转换为一维数字
int arc_toState(const arc &se) {//注意e 一定要加上&,不然不能改变e的值
	int e = 0;
	for (int i = 1; i <= 3; ++i)
		for (int j = 1; j <= 3; ++j)
			e = e * 10 + se[i][j];
	return e;

}
//返回Snode的F值
int F(const Snode &e) {
	return e.g + e.h;
}

bool success(const int& cur, const int &tar) {
	return(H1(cur, tar) == 0);
}
//检查是否可达
bool checkvalid(const int& cur, const int& tar) {
	int cur_n = 0, tar_n = 0;
	arc curr, tarr;
	state_toArc(cur, curr);
	state_toArc(tar, tarr);
	for (int i = 0; i < 9; i++)
	{
		for (int j = 0; j < i; j++)//找第i+1个元素后面所有的非0元素,如果元素比它小,那就对应逆序数+1
		{

			if ((curr[i / 3 + 1][i % 3 + 1] != 0) && (curr[j / 3 + 1][j % 3 + 1] != 0) && (curr[i / 3 + 1][i % 3 + 1] < curr[j / 3 + 1][j % 3 + 1])) cur_n++;
			if ((tarr[i / 3 + 1][i % 3 + 1] != 0) && (tarr[j / 3 + 1][j % 3 + 1] != 0) && (tarr[i / 3 + 1][i % 3 + 1] < tarr[j / 3 + 1][j % 3 + 1])) tar_n++;
		}
	}
	return  (cur_n & 1) == (tar_n & 1);
}

//启发函数1  
int H1(int curState, int tarState)
{
	//找当前状态与目标状态的位置不同的非0数字个数
	int num = 0;
	for (int i = 0; i < 9; i++) {
		if ((curState % 10 != tarState % 10)) ++num;
		curState /= 10;
		tarState /= 10;
	}
	return num;
}
//启发函数2
int H2(int curState, int tarState) {
	//找当前状态要移动到目标的最短路径,返回所有状态的最短路径之和
	int num = 0;
	//引入一种新的数组,这个数组的下标值代表的是元素(即key值代表的是value),value值代表的是位置值。
	int cu[9], ta[9];
	int cur = curState, tar = tarState;
	for (int i = 8; i >= 0; i--) {
		cu[cur % 10] = ta[tar % 10] = i;
		cur /= 10;
		tar /= 10;
	}
	//含义是,数字1-8的状态序列到目的序列,它们不同的位置值之差,cu[i] i代表是哪个位置,值代表的是它的位置值0-8。
	//位置值之间的移动的最小步骤不是简单相减,而是要根据它在矩阵上的结构特点,比如位置值0和3,实际上移动过去只需要一步
	//位置值0-8在3*3矩阵上具体的位置移动值应该是 |xc-xt|+|yc-yt|
	for (int i = 1; i <= 8; i++) {
		num += abs(cu[i] / 3 - ta[i] / 3) + abs(cu[i] % 3 - ta[i] % 3);
	}

	return num;
}
//启发函数3
int H3(int curState)
{   //返回逆序数目*3
	int num = 0;
	arc curr;
	state_toArc(curState, curr);
	for (int i = 0; i < 9; i++) {
		for (int j = i + 1; j < 9; j++)
		{
			if ((curr[i / 3 + 1][i % 3 + 1] != 0) && (curr[j / 3 + 1][j % 3 + 1] != 0) && (curr[i / 3 + 1][i % 3 + 1] > curr[j / 3 + 1][j % 3 + 1]))
				++num;
		}
	}

	return num * 3;
}
//启发函数4
int H4(int curState, int tarState) {
	//综合H1与H3
	return (H1(curState, tarState) + H3(curState));
}

void inPut(arc s) {
	for (int i = 1; i <= 3; i++) {
		for (int j = 1; j <= 3; j++) {
			cin >> s[i][j];
		}
	}
}

void outPut(arc tmp) {
	for (int i = 1; i <= 3; ++i) {
		for (int j = 1; j <= 3; ++j)
			cout << tmp[i][j] << " ";
		puts("");
	}
}

//运行open算法,返回值取最短路径的step值
int run(const int&st, const int &ed , int H) {
	if (!checkvalid(st, ed)) return -1;//检测是否可达
	//清理上一次程序运行时产生的数据。
	open_key.clear();
	open_value.clear();
	closed.clear();
	path.clear();
	/*将初始结点放入open表中*/
	int index = 0;       //递增的序号值,唯一标识
	/*此处H2可修改为H1\2\3\4*/
	switch (H) {
	case 1:H = H1(st, ed); break;
	case 2:H = H2(st, ed); break;
	case 3:H = H3(st); break;
	case 4:H = H4(st, ed); break;
	}
	//cout << "该节点的H为:"<
	Snode start(st, 0, H, index++, -1);    //初始化初始结点start的值,其前驱路径的标号是-1,代表不存在
	open_key.insert(make_pair(H, start));   //将初始结点放入open_key表中
	open_value.insert(make_pair(start, H));//将初始结点放入open_value表中
	//cout<< (open_key.begin()->second).state;
	/*对后续结点进行启发式搜索*/
	while (open_key.size())
	{
		Snode mixNode = open_key.begin()->second;                    //取出open的第一个元素(该元素也是f值最小的结点)进行扩展
		open_key.erase(open_key.begin());                                      //从open表中清除
		open_value.erase(open_value.lower_bound(mixNode));      
		closed.insert(make_pair(mixNode.state, true));                    //将序列放入closed表中
		path.push_back(mixNode);                                                   //将结点的具体结构放入path中,push_back()放在队尾

	/*如果是已经到达目标路径,返回最短路径值step*/
		if (success(mixNode.state, ed)) {
			return mixNode.g;       //g即步数
		}
	/*对取出的结点进行移动操作,生成新的子节点。*/
		/*寻找空格的位置*/
		int cx = -1, cy = -1;//cx,cy代表空格的坐标[cx,cy]
		arc tmp;int tmps;state_toArc(mixNode.state, tmp);
		for (int i = 1; i <= 3; i++) {
			if (cx != -1) break;
			for (int j = 1; j <= 3; j++) {
				if (tmp[i][j] == 0)
				{
					cx = i;cy = j;
					break;
				}}}
		/*移动生成子结点*/
		for (int k = 0; k < 4; k++) {
			int nx = cx + move_x[k];
			int ny = cy + move_y[k];
			if (nx >= 1 && nx <= 3 && ny >= 1 && ny <= 3)//保证移动后的空格不越界
			{
				swap(tmp[cx][cy], tmp[nx][ny]); //将原来空格和移动后的空格应在的位置的元素,交换顺序
				tmps = arc_toState(tmp);             //提取出新生成的状态序列state值
				swap(tmp[cx][cy], tmp[nx][ny]);//还原序列,用于空格其他方向的移动
               /*对新生成的子节点进行查重判定是否入open表中*/
			   Snode newNode(tmps, mixNode.g + 1, H2(tmps, ed), index++, mixNode.cur);//初始化新生成的子节点
				//新生成的子节点的state是tmps,深度是父节点mixNode的深度+1,h用判定函数h(x)求得,序列号按序+1,父节点序列号是mixNode的序列号cur
				int newF = F(newNode);//保存新结点的F值

				//查找新生成结点的state是否在closed表中
				if (!closed.count(tmps))//如果不在closed表中,执行以下操作
				{
			/*查找新生成结点的state是否在open表中*/
					/*双map表,key(f)-value(state),value-key,在查找元素的时候,不用遍历,直接利用low_bound()函数找value-key表中的的first值value*/
	    			map<Snode, int>::iterator it_v = open_value.lower_bound(newNode);
					//如果该元素的first值恰好=newNode,说明newNode的序列值state在open表中存在
					map<int, Snode>::iterator it_k;//创建指向key-value表迭代器的指针it_k
					
				 /*如果在open表中*/
					if (it_v != open_value.end() && it_v->first == newNode) {
						//如果新生成的结点是相对于原open表中结点的最优解
						if ((newNode.g )< ((it_v->first).g)) {
							for (it_k = open_key.lower_bound(F(it_v->first)); it_k != open_key.upper_bound(F(it_v->first)); ++it_k)
							{
								if (it_k->second == newNode) break;//找到open表中重复结点
							}
							//删除原有open表中结点
							open_key.erase(it_k);
							open_value.erase(it_v);
							//将新结点加入open表中
							open_key.insert(make_pair(newF, newNode));
							open_value.insert(make_pair(newNode, newF));
						}
						//不是最优解,放弃新生成的结点
					}
					//既不在open表中,也不在closed表中
					else {
						//将新结点加入open表中
						open_key.insert(make_pair(newF,newNode));
						open_value.insert(make_pair(newNode,newF));
					}
				}
				else if (closed.count(tmps))  //如果在closed表中,执行以下操作
				{
					//不用判断是否在open表中,因为想先入closed表必须先入open表,想插入一个结点到open表时,如果它已经在closed表中,是不会把它放到open表里的
					//close只是一个int状态,Snode结构在path里,故查path
					int old;
					for (old = 0; old < path.size(); old++) {
						if (path[old] == newNode) break;//Snode类型的==号代表state值相同
					}
					//如果是更优解
					if (newNode.g < path[old].g) {
						//将原closed表中的元素删除,将新结点放入open表中。新结点的路径在新结点初始化时就已经保存,新结点的f值在加入open表时进行保存
						closed.erase(closed.lower_bound(newNode.state));
						path.erase(path.begin() + old);
						//将新结点加入open表中
						open_key.insert(make_pair(newF, newNode));
						open_value.insert(make_pair(newNode,newF));
					}
					//如果不是更优解,舍弃新生成的结点newNode
				}
				/*对新生成的子节点进行判定是否入open表结束*/
			}
		}/*对移动生成的新结点的整个操作结束*/
	}
	//函数走到这里,代表open表为空,则无解,返回步骤-1*/
	return -1;//一般不会走到这一步
}
//要从后向前找到最优解的路径,采取的是后序遍历,那必然传递参数有pre,有step(第几步),由于查找时是查找path上的路径,path是依次进栈的方法加入元素
//故pre元素必定在子元素的前面

void findPath(int pre, int size, int step) {
	if (step == -1) {
		return;

	}
	else if (step == 0) {
		cout << "Step-->" << step << endl;
		cout << endl;
		arc tmp;
		state_toArc(path[size].state, tmp);
		outPut(tmp);
		cout << endl;
		return;
	}
	for (int i = size; i >= 0; i--) {
		//找到path中原结点的pre结点,递归调用函数,输出结果
		if (path[i].cur == pre) findPath(path[i].pre, i, step - 1);
	}
	cout << "Step--> " << step << endl;
	cout << endl;
	arc tmp;
	state_toArc(path[size].state, tmp);
	outPut(tmp);
	cout << endl;

}


void goPath(int step) {
	findPath((path.end() - 1)->pre, path.size() - 1, step);
}


4.main.cpp

#include "stdafx.h"
#include"global.h"
#include"function.h"
#include"windows.h"
#include"ctime"
int main()
{
	double time = 0;
	double counts = 0;
	LARGE_INTEGER nFreq;
	LARGE_INTEGER nBeginTime;
	LARGE_INTEGER nEndTime;
	QueryPerformanceFrequency(&nFreq);
	QueryPerformanceCounter(&nBeginTime);//开始计时

/*	clock_t start, end;
	start = clock();*/
	system("color F0");
	arc st, ed;
	freopen("1.txt", "r+", stdin);
	inPut(st);
	inPut(ed);
	/*cout << "初始矩阵为:\n";
	 outPut(st);
	 cout << "目标矩阵为:\n";
	 outPut(ed);*/
	//run(arc_toState(st), arc_toState(ed));
	freopen("CON", "r", stdin);//暂停函数之前,使用freopen重定向输入回控制台
	/*cout<<"请选择你想使用的评估函数"
	int H;cin>>H;*/
	int H =1;
	int step = run(arc_toState(st), arc_toState(ed),H);
	if (step != -1)
	{
		cout << "有解,使用H"<<H<<"评估算法,最短路径需要" << step<<"步" << endl;
		goPath(step);
	}
	else {
		cout << "不可达" << endl;
	}
	//cout << arc_toState(st);
	/*end = clock();
	cout << "使用H"<
	QueryPerformanceCounter(&nEndTime);//停止计时
	time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;//计算程序执行时间单位为s
	cout << "使用H" << H << " 评估算法,程序执行时间:" << time * 1000 << "ms" << endl;

	freopen("CON", "r", stdin);//暂停函数之前,使用freopen重定向输入回控制台
	system("pause");// system("pause>nul");
    return 0;
}


5.1.txt

1 0 3 7 2 4 6 8 5
1 2 3 8 0 4 7 6 5
1 3 2 4 0 5 6 7 8
1 2 3 8 0 4 7 6 5
0 1 2 3 4 5 6 7 8
8 0 6 5 4 7 2 3 1

在这个txt里每两行是一个初始状态和目标状态,共设置了三组,一组一组上下变换顺序即可。删除和添加的时候也要一组一组的修改。

最后的效果就是这样:
人工智能大作业----八数码问题_第1张图片

你可能感兴趣的:(人工智能)