八数码(A*+路径输出+逆序数+康托展开式hash)

九宫重排

问题描述

给定九宫格的初始状态要求在有限步的操作内使其转化为目标状态且所得到的解是代价最小解(即移动的步数最少)。


基本要求

输入九宫格的初始状态和目标状态

输出重排的过程即途径的状态

空格用0来表示

BFS 做法,状态数为9!,可以用set存,就是效率很慢

#include<cstdio>
#include<queue>
#include<algorithm>
#include<iostream>
#include<set>
using namespace std;


struct Dig
{
	int p[9];//当前的状态
	int len;//步数
	int pos;//空格所在的位置
	int ans[32][9];//存取路径 因为最长的路径不可能大于2的32次方
};

set<int>s;
int dir[4] = { 1,-1,3,-3 };


Dig start;
//2 8 3
//1 0 4
//7 6 5

void bfs()
{
	queue<Dig>q;
	q.push(start);
	while (!q.empty())
	{
		Dig tmp1 = q.front();
		if (tmp1.p[0] == 1 && tmp1.p[1] == 2 && tmp1.p[2] == 3 && tmp1.p[3] == 8 && tmp1.p[4] == 0 && tmp1.p[5] == 4 && tmp1.p[6] == 7 && tmp1.p[7] == 6 && tmp1.p[8] == 5)
		{
			printf("You must move it at least %d times\n", tmp1.len);
			for (int i = 0; i <= tmp1.len; i++)
			{
				printf("The %d times move:\n", i);
				for (int j = 0; j < 9; j++)
				{
					if (j % 3 == 0)puts("");
					printf("%5d", tmp1.ans[i][j]);
				}
				puts("");
			}

			return;
		}
		tmp1.len++;
		q.pop();
		for (int i = 0; i < 4; i++)
		{
			Dig tmp2 = tmp1;
			tmp2.pos = tmp2.pos + dir[i];
			if (dir[i] == 1 && (tmp2.pos == 3 || tmp2.pos == 6))continue;//空格在最左边不能-1
			if (dir[i] == -1 && (tmp2.pos == 2 || tmp2.pos == 5))continue;//同理
			if (tmp2.pos >= 0 && tmp2.pos < 9)//必须在范围内
			{
				swap(tmp2.p[tmp2.pos], tmp2.p[tmp2.pos - dir[i]]);//移动
				int status = 0;
				for (int i = 0; i < 9; i++)
					status = status * 10 + tmp2.p[i] + 1;

				if (s.count(status)==0) { //没有重复出现
					for (int i = 0; i < 9; i++)
						tmp2.ans[tmp2.len][i] = tmp2.p[i];
					s.insert(status);
					q.push(tmp2); }
			}
		}

	}
	puts("找不到解");
}
int main()
{
	while (1)
	{
		puts("Input the inital status:");
		for (int i = 0; i < 9; i++)
		{
			scanf("%d", &start.p[i]);
			if (start.p[i] == 0)start.pos = i;
			start.ans[0][i] = start.p[i];
		}
		start.len = 0;
		s.clear();
		int value=0;
		for (int i = 0; i < 9; i++)//0 1 2 3 4 5 6 7 8 -->123456789
			value = value * 10 + (start.p[i] + 1);
		s.insert(value);
		bfs();
	}
	
}
BFS+康托hash判重就快了很多了


#include<cstdio>
#include<queue>
#include<algorithm>
#include<iostream>
using namespace std;


struct Dig
{
	int p[9];
	int len;
	int pos;
	int ans[32][9];
};

int  fac[] = { 1,1,2,6,24,120,720,5040,40320 };
bool vis[555555];
int dir[4] = { 1,-1,3,-3 };


Dig start;
//2 8 3
//1 0 4
//7 6 5

int KT(Dig s)//康托展开式hash
{
	int i, j, t, sum;
	sum = 0;
	for (i = 0; i<9; i++)
	{
		t = 0;
		for (j = i + 1; j<9; j++)
			if (s.p[j] < s.p[i])
				t++;
		sum += t*fac[9 - i - 1];
	}
	return sum + 1;
}

void bfs()
{
	queue<Dig>q;
	q.push(start);
	while (!q.empty())
	{
		Dig tmp1 = q.front();
		if (tmp1.p[0] == 1 && tmp1.p[1] == 2 && tmp1.p[2] == 3 && tmp1.p[3] == 8 && tmp1.p[4] == 0 && tmp1.p[5] == 4 && tmp1.p[6] == 7 && tmp1.p[7] == 6 && tmp1.p[8] == 5)
		{
			printf("You must move it at least %d times\n", tmp1.len);
			for (int i = 0; i <= tmp1.len; i++)
			{
				printf("The %d times move:\n", i);
				for (int j = 0; j < 9; j++)
				{
					if (j % 3 == 0)puts("");
					printf("%5d", tmp1.ans[i][j]);
				}
				puts("");
			}

			return;
		}
		tmp1.len++;
		q.pop();
		for (int i = 0; i < 4; i++)
		{
			Dig tmp2 = tmp1;
			tmp2.pos = tmp2.pos + dir[i];
			if (dir[i] == 1 && (tmp2.pos == 3 || tmp2.pos == 6))continue;
			if (dir[i] == -1 && (tmp2.pos == 2 || tmp2.pos == 5))continue;
			if (tmp2.pos >= 0 && tmp2.pos < 9)
			{
				swap(tmp2.p[tmp2.pos], tmp2.p[tmp2.pos - dir[i]]);
				int status = KT(tmp2);
				if (!vis[status]) { 
					for (int i = 0; i < 9; i++)
						tmp2.ans[tmp2.len][i] = tmp2.p[i];
					vis[status] = true;
					q.push(tmp2); }
			}
		}

	}
	puts("找不到解");
}
int main()
{
	while (1)
	{
		puts("Input the inital status:");
		for (int i = 0; i < 9; i++)
		{
			scanf("%d", &start.p[i]);
			if (start.p[i] == 0)start.pos = i;
			start.ans[0][i] = start.p[i];
		}
		start.len = 0;
		memset(vis, false, sizeof(vis));
		vis[KT(start)] = true;
		bfs();
	}
	
}

用A*的话,如果八数码无解还是和BFS的效率一样,所以加个逆序数判断有无解

这个A*的函数是  f(n)=g(n)+h(n)     

g(n)代表步数

h(n)代表当前八数码不在位的个数

剪枝原理:

       逆序数:对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。逆序对的总数称为逆序数

       只要终止状态和起始状态的逆序数(空的位置不算)奇偶性不同,就一定不能达到目标状态。

       分析:向左或者向右移动,逆序数的奇偶行不变....0,xt,xt+1...,将.0和xt交换,奇偶性是不变的

       对 x1  x2  x3

           x4   x5  x6

           x7   0    x8

      将0和x5交换,x1  x2  x3  x4   x5  x6 x7   0    x8,下面分三种情况

     a)若 x5>x6 && x5> x7,则逆序数+2

     a)若 x5 <x6 && x5<x7,则逆序数-2

     a)若 x5 在6 和x7之间,则逆序数不变

通过以上分析可知:只有起始状态可终止状态逆序数奇偶性相同才能转换


#include<cstdio>
#include<queue>
#include<algorithm>
#include<iostream>
using namespace std;


struct Dig
{
	int p[9];
	int len;
	int pos;
	int ans[32][9];
	int cost;
	bool operator < (const Dig &a) const {
		return cost>a.cost;//最小值优先
	}
};

int  fac[] = { 1,1,2,6,24,120,720,5040,40320 };
bool vis[555555];
int dir[4] = { 1,-1,3,-3 };


Dig start;
//2 8 3
//1 0 4
//7 6 5

bool inverse(Dig s)
{
	int t = 0, x, y;
	for (int i = 1; i<9; i++)
		for (int j = 0; j<i; j++)
		{
			if (s.p[j] == 0)continue;
			else x = s.p[j];
			if (s.p[i] == 0)continue;
			else y = s.p[i];
			if (x>y)
				t++;
		}
	if (t & 1)
		return true;
	return false;
}
int KT(Dig s)//康托展开式hash
{
	int i, j, t, sum;
	sum = 0;
	for (i = 0; i<9; i++)
	{
		t = 0;
		for (j = i + 1; j<9; j++)
			if (s.p[j] < s.p[i])
				t++;
		sum += t*fac[9 - i - 1];
	}
	return sum + 1;
}

int fcost(Dig tmp1)
{
	int cnt = 9;
	if (tmp1.p[0] == 1)cnt--;
	if (tmp1.p[1] == 2)cnt--; 
	if (tmp1.p[2] == 3)cnt--;
	if (tmp1.p[3] == 8)cnt--;
	if (tmp1.p[4] == 0)cnt--;
	if (tmp1.p[5] == 4)cnt--;
	if (tmp1.p[6] == 7)cnt--;
	if (tmp1.p[7] == 6)cnt--;
	if (tmp1.p[8] == 5)cnt--;
	return cnt;
}

void Astar()
{
	priority_queue<Dig>q;
	q.push(start);
	while (!q.empty())
	{
		Dig tmp1 = q.top();
		if (tmp1.p[0] == 1 && tmp1.p[1] == 2 && tmp1.p[2] == 3 && tmp1.p[3] == 8 && tmp1.p[4] == 0 && tmp1.p[5] == 4 && tmp1.p[6] == 7 && tmp1.p[7] == 6 && tmp1.p[8] == 5)
		{
			printf("You must move it at least %d times\n", tmp1.len);
			for (int i = 0; i <= tmp1.len; i++)
			{
				printf("The %d times move:\n", i);
				for (int j = 0; j < 9; j++)
				{
					if (j % 3 == 0)puts("");
					printf("%5d", tmp1.ans[i][j]);
				}
				puts("");
			}

			return;
		}
		tmp1.len++;
		q.pop();
		for (int i = 0; i < 4; i++)
		{
			Dig tmp2 = tmp1;
			tmp2.pos = tmp2.pos + dir[i];
			if (dir[i] == 1 && (tmp2.pos == 3 || tmp2.pos == 6))continue;
			if (dir[i] == -1 && (tmp2.pos == 2 || tmp2.pos == 5))continue;
			if (tmp2.pos >= 0 && tmp2.pos < 9)
			{
				swap(tmp2.p[tmp2.pos], tmp2.p[tmp2.pos - dir[i]]);
				int status = KT(tmp2);
				if (!vis[status]) { 
					for (int i = 0; i < 9; i++)
						tmp2.ans[tmp2.len][i] = tmp2.p[i];
					tmp2.cost = tmp2.len + fcost(tmp2);
					vis[status] = true;
					q.push(tmp2); }
			}
		}

	}
}
int main()
{
	while (1)
	{
		puts("Input the inital status:");
		for (int i = 0; i < 9; i++)
		{
			scanf("%d", &start.p[i]);
			if (start.p[i] == 0)start.pos = i;
			start.ans[0][i] = start.p[i];
		}
		if (!inverse(start))
		{
			printf("unsolvable\n");
			continue;
		}
		start.len = 0;
		start.cost = start.len + fcost(start);
		memset(vis, false, sizeof(vis));
		vis[KT(start)] = true;
		Astar();
	}
	
}

输入目标状态

#include<cstdio>
#include<queue>
#include<algorithm>
#include<iostream>
using namespace std;


struct Dig
{
	int p[9];
	int len;
	int pos;
	int ans[32][9];
	int cost;
	bool operator < (const Dig &a) const {
		return cost>a.cost;//最小值优先
	}
};

int  fac[] = { 1,1,2,6,24,120,720,5040,40320 };
bool vis[555555];
int dir[4] = { 1,-1,3,-3 };


Dig start;
//2 8 3
//1 0 4
//7 6 5
Dig last;

bool inverse(Dig s)
{
	int t = 0, x, y;
	for (int i = 1; i<9; i++)
		for (int j = 0; j<i; j++)
		{
			if (s.p[j] == 0)continue;
			else x = s.p[j];
			if (s.p[i] == 0)continue;
			else y = s.p[i];
			if (x>y)
				t++;
		}
	if (t & 1)return true;
	return false;
}

void input()
{
	puts("Input the inital status:");
	for (int i = 0; i < 9; i++)
	{
		scanf("%d", &start.p[i]);
		if (start.p[i] == 0)start.pos = i;
		start.ans[0][i] = start.p[i];
	}
	puts("Input the last status:");
	for (int i = 0; i < 9; i++)
	{

		scanf("%d",&last.p[i]);
	}
}

int KT(Dig s)//康托展开式hash
{
	int i, j, t, sum;
	sum = 0;
	for (i = 0; i<9; i++)
	{
		t = 0;
		for (j = i + 1; j<9; j++)
			if (s.p[j] < s.p[i])
				t++;
		sum += t*fac[9 - i - 1];
	}
	return sum + 1;
}

int fcost(Dig s)
{
	int cnt = 0;
	for (int i = 0; i <9 ; i++)
	{
		if (s.p[i] != last.p[i])cnt++;
	}
	return cnt;
}

void Astar()
{
	priority_queue<Dig>q;
	bool OK ;
	q.push(start);
	while (!q.empty())
	{
		Dig tmp1 = q.top();
		OK = true;
		for (int i = 0; i < 9; i++)
		{
			if (tmp1.p[i] != last.p[i]) { OK = false; break; }
		}
		if (OK)
		{
			printf("You must move it at least %d times\n", tmp1.len);
			for (int i = 0; i <= tmp1.len; i++)
			{
				printf("The %d times move:\n", i);
				for (int j = 0; j < 9; j++)
				{
					if (j % 3 == 0)puts("");
					printf("%5d", tmp1.ans[i][j]);
				}
				puts("");
			}

			return;
		}
		tmp1.len++;
		q.pop();
		for (int i = 0; i < 4; i++)
		{
			Dig tmp2 = tmp1;
			tmp2.pos = tmp2.pos + dir[i];
			if (dir[i] == 1 && (tmp2.pos == 3 || tmp2.pos == 6))continue;
			if (dir[i] == -1 && (tmp2.pos == 2 || tmp2.pos == 5))continue;
			if (tmp2.pos >= 0 && tmp2.pos < 9)
			{
				swap(tmp2.p[tmp2.pos], tmp2.p[tmp2.pos - dir[i]]);
				int status = KT(tmp2);
				if (!vis[status]) { 
					for (int i = 0; i < 9; i++)
						tmp2.ans[tmp2.len][i] = tmp2.p[i];
					tmp2.cost = tmp2.len + fcost(tmp2);
					vis[status] = true;
					q.push(tmp2); }
			}
		}

	}
}
int main()
{
	while (1)
	{
		input();
		bool invs = inverse(start);
		bool invl= inverse(last);
		if (invs!=invl)
		{
			printf("unsolvable\n");
			continue;
		}
		start.len = 0;
		start.cost = start.len + fcost(start);
		memset(vis, false, sizeof(vis));
		vis[KT(start)] = true;
		Astar();
	}
	
}

A* +曼哈顿距离


#include<cstdio>
#include<queue>
#include<algorithm>
#include<iostream>
using namespace std;


struct Dig
{
	int p[9];
	int len;
	int pos;
	int ans[32][9];
	int cost;
	bool operator < (const Dig &a) const {
		return cost>a.cost;//最小值优先
	}
};
int dir[4] = { 1,-1,3,-3 };
int  fac[] = { 1,1,2,6,24,120,720,5040,40320 };
bool vis[555555];
bool first;
int lpos[9];



Dig start;
//2 8 3 1 0 4 7 6 5
//1 2 3 8 0 4 7 6 5
//2 1 6 4 0 8 7 5 3->18steps
Dig last;

bool inverse(Dig s)
{
	int t = 0, x, y;
	for (int i = 1; i<9; i++)
		for (int j = 0; j<i; j++)
		{
			if (s.p[j] == 0)continue;
			else x = s.p[j];
			if (s.p[i] == 0)continue;
			else y = s.p[i];
			if (x>y)
				t++;
		}
	if (t & 1)return true;
	return false;
}

void input()
{
	puts("Input the inital status:");
	for (int i = 0; i < 9; i++)
	{
		scanf("%d", &start.p[i]);
		if (start.p[i] == 0)start.pos = i;
		start.ans[0][i] = start.p[i];
	}
	puts("Input the last status:");
	for (int i = 0; i < 9; i++)
	{

		scanf("%d",&last.p[i]);
	}
}

int KT(Dig s)//康托展开式hash
{
	int i, j, t, sum;
	sum = 0;
	for (i = 0; i<9; i++)
	{
		t = 0;
		for (j = i + 1; j<9; j++)
			if (s.p[j] < s.p[i])
				t++;
		sum += t*fac[9 - i - 1];
	}
	return sum + 1;
}

int fcost(Dig s)//manhattan距离
{
	int mlen = 0,x,y;
	if (first)
	{
		for (int i = 0; i < 9; i++)
		{
			lpos[last.p[i]] = i;
		}
		first = false;
	}
	for (int i = 0; i < 9; i++)
	{
		x = i % 3;
		y = i / 3;
		mlen += abs(lpos[s.p[i]]%3-x)+abs(lpos[s.p[i]]/3-y);
	}
	return mlen;
}

void Astar()
{
	priority_queue<Dig>q;
	bool OK ;
	q.push(start);
	while (!q.empty())
	{
		Dig tmp1 = q.top();
		OK = true;
		for (int i = 0; i < 9; i++)
		{
			if (tmp1.p[i] != last.p[i]) { OK = false; break; }
		}
		if (OK)
		{
			printf("You must move it at least %d times\n", tmp1.len);
			for (int i = 0; i <= tmp1.len; i++)
			{
				printf("The %d times move:\n", i);
				for (int j = 0; j < 9; j++)
				{
					if (j % 3 == 0)puts("");
					printf("%5d", tmp1.ans[i][j]);
				}
				puts("");
			}

			return;
		}
		tmp1.len++;
		q.pop();
		for (int i = 0; i < 4; i++)
		{
			Dig tmp2 = tmp1;
			tmp2.pos = tmp2.pos + dir[i];
			if (dir[i] == 1 && (tmp2.pos == 3 || tmp2.pos == 6))continue;
			if (dir[i] == -1 && (tmp2.pos == 2 || tmp2.pos == 5))continue;
			if (tmp2.pos >= 0 && tmp2.pos < 9)
			{
				swap(tmp2.p[tmp2.pos], tmp2.p[tmp2.pos - dir[i]]);
				int status = KT(tmp2);
				if (!vis[status]) { 
					for (int i = 0; i < 9; i++)
						tmp2.ans[tmp2.len][i] = tmp2.p[i];
					tmp2.cost = tmp2.len + fcost(tmp2);
					vis[status] = true;
					q.push(tmp2); }
			}
		}

	}
}
int main()
{
	while (1)
	{
		input();
		bool invs = inverse(start);
		bool invl= inverse(last);
		if (invs!=invl)
		{
			printf("unsolvable\n");
			continue;
		}
		first = true;
		start.len = 0;
		start.cost = start.len + fcost(start);
		memset(vis, false, sizeof(vis));
		vis[KT(start)] = true;
		Astar();
	}
	
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值