POJ1077、HDU1043 Eight 八数码问题:双向BFS、A*

Problem Address:http://poj.org/problem?id=1077

Problem Address:http://acm.hdu.edu.cn/showproblem.php?pid=1043


【前言】


第一次写八数码问题。

这份代码写得也是够残的。

还好效率不是很低。

HDU的数据明显比POJ的强。而且HDU里有初末状态相同的情况,而POJ没有。

这也是后来改成双向A*之后在HDU上狂WA的原因。

提交了20多次之后才发现真相= =


【思路】


典型的八数码问题。

九宫格中有1-8的数字,空出一位用于数字的移动。求从一个状态转移到另一个状态的路径。

具体的存储方式可以到网上搜一下。

以下简单介绍几种应用于八数码问题的搜索算法。


DFS:

由于不知道步数,所以用DFS是不可取的。


BFS:

从初始状态开始,每一步向四个方向扩展。直到达到末状态。

简单的BFS使用的空间很大,时间也很大,当步数增加时节点数膨胀很快。

因为,就八数码问题,一般的BFS不太可取。


双向BFS:

从初状态和末状态同时开始扩展。扩展方式与一般BFS相同。

直到两者有交点时停止查找。

相比于一般的BFS,双向BFS无论是在空间还是时间方面,都有很大的提升。


A*启发式搜索:

利用启发式函数 f'() = a*g'() + b*h'(),g'()表示当前以使用代价(可用步数表示),h'()表示达到目标还需花费的代价(可用manhattan距离或者相同位置不同数字的个数表示)。

启发式搜索相对于前面的搜索提升是巨大的。访问的结点很少,空间和时间的使用也因此很少。

可以这样看,BFS都是A*的一种特殊形式。

当b=0且a!=0时,f'() = a*g'(),即只于结点所耗步数有关,即为一般的BFS。

所以,通过改变a和b的值可以在一定程度上提升搜索效率。


双向A*:

A*与双向BFS的结合。


【代码】


这份代码是双向A*,但是通过改变里面的参数可以实现BFS、双向BFS、A*等。


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

const int maxhash = 400000;

int start, end = 123456789;

int le[9] = {1,10,100,1000,10000,100000,1000000,10000000,100000000};
int te[9] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};

int manhattan[9][9] = {
	{0,1,2,1,2,3,2,3,4},
	{1,0,1,2,1,2,3,2,3},
	{2,1,0,3,2,1,4,3,2},
	{1,2,3,0,1,2,1,2,3},
	{2,1,2,1,0,1,2,1,2},
	{3,2,1,2,1,0,3,2,1},
	{2,3,4,1,2,3,0,1,2},
	{3,2,3,2,1,2,1,0,1},
	{4,3,2,3,2,1,2,1,0}};

int re[9];

struct HASH
{
	bool visited;
	int value;
	int pre;
	char op;
	bool inqueue;
	int g;
	int h;
	bool opposite;
}hash[maxhash];

struct cmp
{
	bool operator()(const int &a, const int &b)
	{
		int fa = hash[a].g + 10*hash[a].h;
		int fb = hash[b].g + 10*hash[b].h;
		return fa>fb;
	}
};

char get_opposite(char op)
{
	switch(op)
	{
	case 'u':
		return 'd';
	case 'd':
		return 'u';
	case 'l':
		return 'r';
	case 'r':
		return 'l';
	default:
		return 'x';
	}
}

priority_queue<int, vector<int>, cmp> q;

void init_hash()
{
	int i;
	for (i=0; i<maxhash; i++)
	{
		hash[i].g = hash[i].h = 0;
		hash[i].inqueue = false;
		hash[i].visited = false;
		hash[i].pre = -1;
	}
}

void init_rank(int x)
{
	int i;
	int t = x%10;
	x /= 10;
	for (i=7; i>=0; i--)
	{
		if (i+1<t) re[x%10-1] = i;
		else re[x%10-1] = i+1;
		x /= 10;
	}
	re[8] = t-1;
}

bool check_even(int x)
{
	int temp[8];
	int i, j;
	x /= 10;
	for (i=7; i>=0; i--)
	{
		temp[i] = x%10;
		x /= 10;
	}
	int sum = 0;
	for (i=1; i<8; i++)
	{
		for (j=0; j<i; j++)
		{
			if (temp[i]<temp[j])
				sum++;
		}
	}
	if (sum&1==1) return false;
	else return true;
}

int get_manhattan(int x, bool opposite)
{
	int i;
	int h = 0;
	int t = x%10;
	x /= 10;
	if (opposite==false)
	{
		for (i=7; i>=0; i--)
		{
			if (i+1<t) h += manhattan[x%10-1][i];
			else h += manhattan[x%10-1][i+1];
			x /= 10;
		}
	}
	else
	{
		for (i=7; i>=0; i--)
		{
			if (i+1<t) h += manhattan[i][re[x%10-1]];
			else h += manhattan[i+1][re[x%10-1]];
			x /= 10;
		}
	}
	return h;
}

int move(char op, int x)
{
	int t0, t1, t2;
	switch(op)
	{
	case 'u':
		t0 = 9 - x%10 + 1;
		t1 = x/le[t0];
		t2 = t1%1000;
		t1 = t1-t2 + (t2%100)*10 + t2/100;
		t1 *= le[t0];
		return (t1+(x%le[t0])-3);
	case 'r':
		return x+1;
	case 'd':
		t0 = 9 - x%10 - 2;
		t1 = x/le[t0];
		t2 = t1%1000;
		t1 = t1-t2 + (t2%10)*100 + t2/10;
		t1 *= le[t0];
		return (t1+(x%le[t0])+3);
	case 'l':
		return x-1;
	default:
		return x;
	}
}

int get_hash(int x)
{
	int i, j, k;
	int sum = (9-x%10)*te[8];
	x /= 10;
	int temp[8];
	for (i=7; i>=0; i--)
	{
		temp[i] = x%10;
		x /= 10;
	}
	for (i=1; i<8; i++)
	{
		k = 0;
		for (j=0; j<i; j++)
		{
			if (temp[i]<temp[j])
				k++;
		}
		sum += k*te[i];
	}
	return sum;
}

void show(int x)
{
	if (hash[x].pre==-1)
		return;
	show(hash[x].pre);
	printf("%c", hash[x].op);
}

void show_opposite(int x)
{
	if (hash[x].pre!=-1)
	{
		printf("%c", get_opposite(hash[x].op));
		show_opposite(hash[x].pre);
	}
}

bool valid(int x, char op, int pre)
{
	int hx = get_hash(x);
	if (pre==-1 && !hash[hx].visited)
	{
		if (x==end) hash[hx].opposite = true;
		else hash[hx].opposite = false;
		hash[hx].value = x;
		hash[hx].h = get_manhattan(x, hash[hx].opposite);
		hash[hx].inqueue = true;
		hash[hx].visited = true;
		hash[hx].op = op;
		hash[hx].g = 0;
		hash[hx].pre = -1;
		q.push(hx);
		return true;
	}
	if (!hash[hx].visited)
	{
		hash[hx].g = hash[pre].g + 1;
		hash[hx].op = op;
		hash[hx].pre = pre;
		hash[hx].visited = true;
		hash[hx].inqueue = true;
		hash[hx].opposite = hash[pre].opposite;
		hash[hx].h = get_manhattan(x, hash[hx].opposite);
		hash[hx].value = x;
		q.push(hx);
	}
	else
	{
		if (hash[hx].opposite ^ hash[pre].opposite)
		{
			if (hash[hx].opposite)
			{
				show(pre);
				printf("%c", op);
				show_opposite(hx);
			}
			else
			{
				show(hx);
				printf("%c", get_opposite(op));
				show_opposite(pre);
			}	
			printf("\n");
			return true;
		}
		else if (hash[hx].inqueue && hash[hx].g>hash[pre].g+1)
		{
			hash[hx].g = hash[pre].g + 1;
			hash[hx].op = op;
			hash[hx].pre = pre;
			if (!hash[hx].inqueue)
			{
				hash[hx].inqueue = true;
				q.push(hx);
			}
		}
	}
	return false;
}

bool expand(int t)
{
	int j;
	int temp = t%10;
	int ht = get_hash(t);
	hash[ht].inqueue = false;
	if (temp>3)
	{
		j = move('u', t);
		if (valid(j, 'u', ht)) return true;
	}
	if (temp%3!=0)
	{
		j = move('r', t);
		if (valid(j, 'r', ht)) return true;
	}
	if (temp<7)
	{
		j = move('d', t);
		if (valid(j, 'd', ht)) return true;
	}
	if (temp%3!=1)
	{
		j = move('l', t);
		if (valid(j, 'l', ht)) return true;
	}
	return false;
}

int main()
{
	char str[100];
	int i, j;
	int t;
	while(cin.getline(str, 100))
	{
		start = 0;
		for (i=0,j=0; str[i]!='\0'; i++)
		{
			if (str[i]!=' ')
			{
				if (str[i]=='x')
				{
					t = j + 1;
				}
				else
				{
					start = start*10 + (str[i]-'0');
					j++;
				}
			}
		}
		start = start*10 + t;
		if (!check_even(start))
		{
			printf("unsolvable\n");
			continue;
		}
		if (start==end)
		{
			printf("\n");
			continue;
		}
		init_hash();
		init_rank(start);
		while(!q.empty()) q.pop();
		valid(start, 'x', -1);
		valid(end, 'x', -1);
		while(!q.empty())
		{
			int ht = q.top();
			t = hash[ht].value;
			q.pop();
			if (expand(t)) break;
		}
	}
	return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值