八数码问题的三种解决方式,其三:启发式搜素

启发式搜索的核心是找到一种方法来优化open表,让里面的节点有一个优先度,优先度大的先放进close表中处理,优先度低的后处理,这样就不同于宽度或者深度的每一个都检验,有很大的优化空间,可以节约大量时间,也可以节约空间(原因是一些节点优先度非常低的就不用扩展因此就不会产生后续转态,因此能节约一定的空间)。

启发式算法的核心,就是找到节点与节点之间的区别,再量化这个区别,最后用优先队列维护这个值,按照优先顺序来处理当前节点。

这里使用了两种算法来处理value值,两种算法第一种更简单,第二种更加节约时间(同样参考的大佬的设计思路):

两者的计算时间为980ms/80ms可见第二种方法对节约时间有显著成效,相比于宽度优先的6200ms有非常大的提升

int valueCalculate_1(Node currentNode, Node targetNode) {
	int count = 8;

	for (int i = 0; i < limit; i++)
		if (currentNode.state[i] == targetNode.state[i] && targetNode.state[i] != 0)
			count--;

	return count + currentNode.deep;

}
int valueCalculate_2(Node currentNode, Node targetNode) {
	int count = 0, begin[3][3], end[3][3];           //count记录所有棋子移动到正确位置需要的步数
	for (int i = 0; i < limit; i++) {
		begin[i / 3][i % 3] = currentNode.state[i];
		end[i / 3][i % 3] = targetNode.state[i];
	}

	for (int i = 0; i < 3; i++)   //检查当前图形的正确度
		for (int j = 0; j < 3; j++)
		{
			if (begin[i][j] == 0)
				continue;
			else if (begin[i][j] != end[i][j])
			{
				for (int k = 0; k < 3; k++)
					for (int w = 0; w < 3; w++)
						if (begin[i][j] == end[k][w])
							count = count + fabs(i * 1.0, k * 1.0) + fabs(j * 1.0, w * 1.0);
			}
		}
	return count + currentNode.deep;

}

 

以下为全文代码:

#pragma once
#include<cstdio>
#include<iostream> 
#include<ctime> 
#include<stack>
#include<queue>
using namespace std;

#define limit 9
struct Node {
	int state[limit];
	struct Node* parent;
	int value;
	int deep;
	friend bool operator < (Node A, Node B) //按照value值小的方案构造优先级队列
	{
		return A.value > B.value;
	}
};

priority_queue<Node> openTable;     //openTable优先队列,按照value值排序
queue<Node> closeTable;     //close表,存储需要被处理的队列
stack<Node> Path;     //最终路径
int count1 = 0, count2 = 0;

bool  read(Node& startNode, Node& targetNode) {
	/*初始化*/
	startNode.parent = NULL;	startNode.deep = 0;	startNode.value = 0;
	targetNode.parent = NULL;	targetNode.deep = 0;	targetNode.value = 0;


	cout << "请输入初始状态\n";
	for (int i = 0; i < limit; i++)
		cin >> startNode.state[i];
	cout << "请输入目标状态\n";
	for (int i = 0; i < limit; i++)
		cin >> targetNode.state[i];

	/*可以直接判定八数码是否有解 http://blog.sina.com.cn/s/blog_5d09995201019kjo.html */

	for (int i = 0; i <= limit - 2; i++)
		for (int j = i + 1; j < limit; j++)
			if (startNode.state[i] > startNode.state[j] && startNode.state[i] * startNode.state[j] != 0)
				count1++;

	for (int i = 0; i <= limit - 2; i++)
		for (int j = i + 1; j < limit; j++)
			if (targetNode.state[i] > targetNode.state[j] && targetNode.state[i] * targetNode.state[j] != 0)
				count2++;


	if (count1 % 2 != count2 % 2)
	{
		return false;
	}
	return true;
}

double fabs(double a, double b) {
	return a < b ? b - a : a - b;
}


int valueCalculate_1(Node currentNode, Node targetNode) {
	int count = 8;

	for (int i = 0; i < limit; i++)
		if (currentNode.state[i] == targetNode.state[i] && targetNode.state[i] != 0)
			count--;

	return count + currentNode.deep;

}

int valueCalculate_2(Node currentNode, Node targetNode) {
	int count = 0, begin[3][3], end[3][3];           //count记录所有棋子移动到正确位置需要的步数
	for (int i = 0; i < limit; i++) {
		begin[i / 3][i % 3] = currentNode.state[i];
		end[i / 3][i % 3] = targetNode.state[i];
	}


	for (int i = 0; i < 3; i++)   //检查当前图形的正确度
		for (int j = 0; j < 3; j++)
		{
			if (begin[i][j] == 0)
				continue;
			else if (begin[i][j] != end[i][j])
			{
				for (int k = 0; k < 3; k++)
					for (int w = 0; w < 3; w++)
						if (begin[i][j] == end[k][w])
							count = count + fabs(i * 1.0, k * 1.0) + fabs(j * 1.0, w * 1.0);
			}
		}
	return count + currentNode.deep;

}



bool judge(Node currentNode, Node targetNode) {
	for (int i = 0; i < limit; i++)
	{
		if (currentNode.state[i] != targetNode.state[i])
		{
			return false;
		}
	}
	return true;
}

//产生新节点,加入OPEN表
void moveNode(Node& currentNode, Node targetNode) {
	/* 计算原状态下,空格所在的行列数,从而判断空格可以往哪个方向移动 */
	int originBlank; //定义空格下标
	for (originBlank = 0; originBlank < limit && currentNode.state[originBlank] != 0; originBlank++);//找到空白格

	int x = originBlank / 3, y = originBlank % 3; //获取空格所在行列编号

	for (int i = 0; i < 4; i++) //找到S扩展的子节点,加入open表中
	{
		int move_X = x, move_Y = y;//新空白格坐标
		Node tempNode;

		/*移动空白格*/
		if (0 == i)  move_X = x - 1;
		if (1 == i)	 move_Y = y - 1;
		if (2 == i)  move_X = x + 1;
		if (3 == i)	 move_Y = y + 1;

		int new_Blank = move_X * 3 + move_Y; //空格新的位置

		if (move_X >= 0 && move_X < 3 && move_Y >= 0 && move_Y < 3) //如果移动合法
		{
			/* 交换新旧空白格的内容*/
			tempNode = currentNode;
			tempNode.state[originBlank] = currentNode.state[new_Blank];
			tempNode.state[new_Blank] = 0;

			if (currentNode.parent != NULL && (*currentNode.parent).state[new_Blank] == 0) //如果新节点和爷爷节点一样,舍弃该节点
			{
				continue;
			}

			tempNode.parent = &currentNode;//当前节点指向自己的父亲节点
			tempNode.value = valueCalculate_2(tempNode, targetNode);//value2的计算方案时间复杂度明显减小
			//tempNode.value = valueCalculate_1(tempNode, targetNode);
			tempNode.deep = currentNode.deep + 1;
			openTable.push(tempNode);// 把子节点都加入open表中
		}
	}
}
#include"Aasterisk.h"
int main()
{
	Node originNode, targetNode;
	if (!read(originNode, targetNode))
	{
		cout << "两点之间不可达!";
		system("pause");
		return 0;
	}
	clock_t start, finish;
	start = clock();
	openTable.push(originNode);
	Node ansNode;//用ansNode取得close表的最后的元素,ansNode与targetNode相同
	while (!openTable.empty())
	{
		closeTable.push(openTable.top()); //将open表中优先级最高的元素压入close表中
		openTable.pop(); //剔除open表中优先级最高的元素
		if (!judge(closeTable.back(), targetNode)) //如果当前棋局与目标棋局不相同,则拓展当前节点
		{
			moveNode(closeTable.back(), targetNode);
			//	closeTable.pop(); 注意进行不能出队操作,不然指向对列中的指针会被释放,导致指向错误的内存地址
		}
		else
		{
			ansNode = closeTable.back();
			break;//找到节点就直接退出,因为使用了优先队列维护,所以保证得到的解是最优解或者最优解之一
		}
	}

	while (ansNode.parent != NULL) {
		Path.push(ansNode);//将路径压入栈中,逆序输出
		ansNode = *(ansNode.parent);//指向自己的父节点,所有连接起来就是路径,用栈维护可以保证顺序输出
	}
	Path.push(ansNode);
	cout << "至少要移动" << Path.size() - 1 << "步" << endl;

	/* 输出方案 */
	while (Path.size() != 0)
	{
		for (int i = 0; i < limit; i++)
		{
			cout << Path.top().state[i] << " ";
			if ((i + 1) % 3 == 0)
				cout << endl;
		}
		Path.pop();
		cout << "\n";
	}

	finish = clock();

	cout << (finish - start) << "毫秒" << endl;
	//system("pause");

	return 0;
}
/*
7 2 4 5 0 6 8 3 1
0 1 2 3 4 5 6 7 8
*/

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值