【 OJ 】 HDOJ1043 18年12月2日12:17 [ 37 ]

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/QingCoffe/article/details/84706110

代码参考:http://www.cnblogs.com/AdaByron/archive/2011/09/21/2200969.html

最近由于考试乱七八糟事情,一直没写代码,而且由于这题的一些细节卡了很长时间。

讲道理这题感觉收获很大,至少对于搜索的理解进一步加深。由于不会做A*,所以参考了别人的代码

总体来说思路很简单,但是....细节....还是说下我自己跳进去的坑吧

BFS 和双向BFS很简单不说多少,对于A*算法,之前由于不了解,经过接触后开始其实A*算法并没有啥,它思路就是给搜索方向给点智能性(当然其空间开销是巨大的),因为对于BFS来说搜索的盲目性还是非常大的,它是探索了周围的每一步后由已知推导出最优解,而A*的智能性体现在启发函数上,由于启发函数的存在,导致了探索时,探索的每一步都是可能性最大的,虽然最优解不一定在可能性最大的步骤中,但是由于每一步走可能性最大,所以虽然还没有找到最优解,但是可以说一直在不断的靠近最优解(这就是我理解的所谓的智能性,不在盲目瞎干),例如:搜索的终点在西方的某一处,广搜是利用队列探索每一步的东南西北,四个方向直到找到最优解,而A*,是由于终点在西方,所以我每一步都在不停的往西方探索,虽然还没找到找点,但是不停的在接近......

以上纯属个人理解,我感觉算法思想非常的棒

开始写了个A*算法一直无法给出最优解...非常的苦恼...一直找不到原因后来发现....(肯本就没想到写个曼哈顿距离这里竟然会出现错误)代码如下:

一共2个错误:
//版本1 没有加括号 减法优先级没有除法高 '1'/3先算
int h(Puzzle p) {//启发函数的曼哈顿距离
	int sum = 0;
	for (int i = 0; i < N; i++) {
		if (p.puzzle[i] == 'x')continue;
		if (p.puzzle[i] - '0' != i + 1) {
			sum += abs(p.puzzle[i] - '1' / 3 - i / 3) + abs(p.puzzle[i] - '1' - i % 3);
		}
	}
	return sum;
}
//版本2 加了括号第二个abs没有%3
int h(Puzzle p) {//启发函数的曼哈顿距离
	int sum = 0;
	for (int i = 0; i < N; i++) {
		if (p.puzzle[i] == 'x')continue;
		if (p.puzzle[i] - '0' != i + 1) {
			sum += abs((p.puzzle[i] - '1') / 3 - i / 3) + abs((p.puzzle[i] - '1') - i % 3);
		}
	}
	return sum;
}
//正确的:
int h(Puzzle p) {//启发函数的曼哈顿距离
	int sum = 0;
	for (int i = 0; i < N; i++) {
		if (p.puzzle[i] == 'x')continue;
		if (p.puzzle[i] - '0' != i + 1) {
			sum += abs((p.puzzle[i] - '1') / 3 - i / 3) + abs((p.puzzle[i] - '1')%3 - i % 3);
		}
	}
	return sum;
}

由于忽略这个启发函数h的第2个错误%3找死我了.....头撞墙的那种懂吗?兄弟

下面的3中代码都没有被AC 原因:Memory Limit Exceeded

思路1:BFS+hash(判重 序列号使用cantor序列)

# include<iostream>
# include<string>
# include<queue>
#define N 9
using namespace std;
bool visited[370000];
string path[370000];
const int fac[] = { 1,1,2,6,24,120,720,5040,40320 };//康托序列   
int dri[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };//东 南 西 北
char por[4] = { 'l','u','r','d' };
struct Xpoint {
	int x;
	int y;
};
struct Pulzzle {
	char puzzle[9];//内部标记数组
	Xpoint Xpoint;
}StartPoint, EndPoint;
queue<Pulzzle> q;
bool IsOK(Pulzzle&p) {
	for (int i = 0; i < N-1; i++) {
		if (p.puzzle[i]-'0' != i + 1)
			return false;
	}
	return true;
}
int Cantor(Pulzzle&p) {
	int i, ii, n, sum = 0;
	for (i = 0; i < N; ++i) {
		n = 0;
		for (ii = i + 1; ii < N; ii++) {
			if (p.puzzle[ii] < p.puzzle[i])
				n++;
		}
		sum += n*fac[N - i - 1];
	}
	return sum;
}
void BFS(Pulzzle p) {
	visited[0] = 1;//终点记录被访问
	path[0] = "";
	q.push(p);//入队
	Pulzzle tp;
	int cantorValue;
	while (!q.empty()) {
		p = q.front();
		q.pop();
		tp = p;
		for (int i = 0; i < 4; ++i) {
			tp.Xpoint.x += dri[i][0];
			tp.Xpoint.y += dri[i][1];
			if ((tp.Xpoint.x >= 0 && tp.Xpoint.x < 3) && (tp.Xpoint.y >= 0 && tp.Xpoint.y < 3)) {//在范围内
				tp.puzzle[p.Xpoint.x * 3 + p.Xpoint.y] = tp.puzzle[tp.Xpoint.x * 3 + tp.Xpoint.y];//原位置补现位置数
				tp.puzzle[tp.Xpoint.x * 3 + tp.Xpoint.y] = 'x';//现位置为 x
				cantorValue = Cantor(tp);
				if (!visited[cantorValue]) {
					path[cantorValue] = path[Cantor(p)] + por[i];
					q.push(tp);
					visited[cantorValue] = 1;
				}
			}
			tp = p;
		}//四种方向记录
	}
}
int main(void) {
	int CantorValue;
	for (int i = 0; i < N; ++i) {
		EndPoint.puzzle[i] = (i + 1) + '0';
	}
	EndPoint.puzzle[8] = 'x';
	EndPoint.Xpoint.x = 2;
	EndPoint.Xpoint.y = 2;
	while (1) {
		for (int i = 0; i < N; ++i) {
			cin >> StartPoint.puzzle[i];
			if (StartPoint.puzzle[i] == 'x') {
				StartPoint.Xpoint.x = i / 3;
				StartPoint.Xpoint.y = i % 3;
			}
		}//录入地图
		CantorValue=Cantor(StartPoint);//拿到起始点的康拓值
		memset(visited, 0, sizeof(visited));//访问表全部初始化0
		BFS(EndPoint);//广搜末尾的所有情况
		if (!visited[CantorValue]) {
			cout << "unsolvable" << endl;
		}
		else {
			for(int i=path[CantorValue].length()-1;i>=0;i--)
			cout << path[CantorValue][i];
		}
	}
	system("pause");
	return 0;
}

思路2:双向BFS(起点和终点已知)+hash(判重 cantor序列)+逆序数对奇偶性不变(情况可行性判断)

# include<iostream>
# include<string>
# include<queue>
#define N 9
using namespace std;
bool flag;
int visited[370000];
string path[370000];
const int fac[] = { 1,1,2,6,24,120,720,5040,40320 };//康托序列   
int dri[4][2] = { { 0,1 },{ 1,0 },{ 0,-1 },{ -1,0 } };//东 南 西 北
char negative_dir[4] = { 'l','u','r','d' };//方向与dri相反
char positive_dir[4] = { 'r','d','l','u' };//方向与dri东南西北对应右下左上
string result;
struct p {
	int x, y;
};
struct Puzzle {
	char puzzle[N];
	p Point;
}StartPoint,EndPoint;
queue<Puzzle> pq;//正序队列 startpoint开始
queue<Puzzle> nq;//逆序队列 endpoint开始
int Cantor(Puzzle p) {
	int i, j, n, sum=0;
	for (i = 0; i < N; i++) {
		n = 0;
		for (j = i + 1; j < N; j++) {
			if (p.puzzle[j] < p.puzzle[i])
				n++;
		}
		sum += fac[8 - i] * n;
	}
	return sum;
}
void BFS(void) {
	int cantorValue;
	Puzzle p, tp;
	p = pq.front();
	tp = p;
	pq.pop();
	for (int i = 0; i < 4; ++i) {//四向搜索
		tp.Point.x += dri[i][0];
		tp.Point.y += dri[i][1];
		if ((tp.Point.x >= 0 && tp.Point.x < 3) && (tp.Point.y >= 0 && tp.Point.y < 3)) {//在范围内
			tp.puzzle[p.Point.x * 3 + p.Point.y] = tp.puzzle[tp.Point.x * 3 + tp.Point.y];
			tp.puzzle[tp.Point.x * 3 + tp.Point.y] = 'x';
			cantorValue = Cantor(tp);
			if (!visited[cantorValue]) {
				pq.push(tp);
				visited[cantorValue] = 1;//正序访问
				path[cantorValue] = path[Cantor(p)] + positive_dir[i];
			}
			else {//已经被访问
				if (visited[cantorValue] == 2) {//正反搜索接触到了
					flag = true;//找到了
					reverse(path[cantorValue].begin(), path[cantorValue].end());
					result = path[Cantor(p)] + path[cantorValue];
					return;
				}//else就是自己正向重复遍历
			}
		}
		tp = p;
	}
}
void ReBFS(void) {
	int cantorValue;
	Puzzle p, tp;
	p = nq.front();
	tp = p;
	nq.pop();
	for (int i = 0; i < 4; ++i) {//四向搜索
		tp.Point.x += dri[i][0];
		tp.Point.y += dri[i][1];
		if ((tp.Point.x >= 0 && tp.Point.x < 3) && (tp.Point.y >= 0 && tp.Point.y < 3)) {//在范围内
			tp.puzzle[p.Point.x * 3 + p.Point.y] = tp.puzzle[tp.Point.x * 3 + tp.Point.y];
			tp.puzzle[tp.Point.x * 3 + tp.Point.y] = 'x';
			cantorValue = Cantor(tp);
			if (!visited[cantorValue]) {
				nq.push(tp);
				visited[cantorValue] = 2;//逆序访问
				path[cantorValue] = path[Cantor(p)] +negative_dir[i];
			}
			else {//已经被访问
				if (visited[cantorValue] == 1) {//正反搜索接触到了
					flag = true;//找到了
					reverse(path[Cantor(p)].begin(), path[Cantor(p)].end());
					result = path[cantorValue] + path[Cantor(p)];
					return;
				}//else就是自己正向重复遍历
			}
		}
		tp = p;
	}
}
bool ISOK(Puzzle p) {//判定标准逆序奇偶性不变
	int i,j,n=0;
	for (i = 0; i < N; i++) {
		if (p.puzzle[i] == 'x')	continue;
		for (j = i - 1; j >= 0; j--) {
			if (p.puzzle[j] == 'x')	continue;
			if (p.puzzle[j] < p.puzzle[i])
				n++;
		}
	}
	return n & 1 ? false : true;
}
int main(void) {
	int CantorValue;
	for (int i = 0; i < N; i++) {
		EndPoint.puzzle[i] = i + 1+'0';
	}
	EndPoint.puzzle[8] = 'x';
	EndPoint.Point.x = EndPoint.Point.y = 2;
	while (1) {
		for (int i = 0; i < N; i++) {
			cin >> StartPoint.puzzle[i];
			if (StartPoint.puzzle[i] == 'x') {
				StartPoint.Point.x = i / 3;
				StartPoint.Point.y = i % 3;
			}
		}//录入起始点的信息
		//------------------------------------------
		result = "";
		CantorValue = Cantor(StartPoint);
		path[CantorValue] = path[0] = "";
		flag = false;
		memset(visited, 0, sizeof(visited));//访问表初始化为0
		memset(path, 0, sizeof(path));//初始化路径表
		//--------------------------------------------
		if (!ISOK(StartPoint)) {
			cout << "unsolvable" << endl;
		}
		pq.push(StartPoint);
		nq.push(EndPoint);//正负队列入队
		int qnum;
		while (!flag) {
			qnum = pq.size();
			if (!flag&&qnum--)
				BFS();
			qnum = nq.size();
			if (!flag&&qnum--)
				ReBFS();
		}
		cout << result << endl;//输出结果
	}
	system("pause");
	return 0;
}

思路3:Astar算法+hash(cantor序列判重)+逆序数对奇偶性不变(判断)+启发函数h(曼哈顿距离)

# include<iostream>
# include<string>
# include<queue>
#define N 9
using namespace std;
struct p {
	int x, y;
};
class Puzzle {
public:
	char puzzle[N];
	p Point;//二维数组中的点
	int f, g;//启发函数 f=g+h
	Puzzle() { ; }
}StartPoint, EndPoint;
class cmp{
public:
	bool operator()(Puzzle a,Puzzle b) {
		return a.f > b.f;
	}
};
bool visited[370000];//判断是否访问
string path[370000];//记录每种情况到起点情况的路径
const int fac[] = { 1,1,2,6,24,120,720,5040,40320 };//康托序列   
//--------------顺时针
int dri[4][2] = { { 0,1 },{ 1,0 },{ 0,-1 },{ -1,0 } };//东 南 西 北
char positive_dir[4] = { 'r','d','l','u' };//方向与dri东南西北对应右下左上
//--------------逆时针
//const int dri[4][2] = { { 1,0 },{ 0,1 },{ -1,0 },{ 0,-1 } };//四个方向
//const char positive_dir[4] = { 'd','r','u','l' };//对应走法

string result;
priority_queue<Puzzle, vector<Puzzle>,cmp>pq;//定义一个优先队列
int abs(int a) {
	return a > 0 ? a : -a;
}
int h(Puzzle p) {//启发函数的曼哈顿距离
	int sum = 0;
	for (int i = 0; i < N; i++) {
		if (p.puzzle[i] == 'x')continue;
		if (p.puzzle[i] - '0' != i + 1) {
			sum += abs((p.puzzle[i] - '1') / 3 - i / 3) + abs((p.puzzle[i] - '1') %3- i % 3);
		}
	}
	return sum;
}
int Cantor(Puzzle p) {//计算康拓值用于hash判重
	int i, j, n, sum = 0;
	for (i = 0; i < N; i++) {
		n = 0;
		for (j = i + 1; j < N; j++) {
			if (p.puzzle[j] < p.puzzle[i])
				n++;
		}
		sum += fac[8 - i] * n;
	}
	return sum;
}
bool ISOK(Puzzle p) {//判定标准逆序奇偶性不变 (判断输入八数码是否有解)
	int i, j, n = 0;
	for (i = 0; i < N; i++) {
		if (p.puzzle[i] == 'x')	continue;
		for (j = i - 1; j >= 0; j--) {
			if (p.puzzle[j] == 'x')	continue;
			if (p.puzzle[j] < p.puzzle[i])
				n++;
		}
	}
	return n & 1 ? false : true;
}
void Astar(void) {
	Puzzle p, tp;
	int cantorValue;
	while (!pq.empty()) {
		tp = p = pq.top();
		pq.pop();
		for (int i = 0; i < 4; i++) {
			tp.Point.x += dri[i][0];
			tp.Point.y += dri[i][1];
			if ((tp.Point.x >= 0 && tp.Point.x < 3) && (tp.Point.y >= 0 && tp.Point.y < 3)) {//在范围内
				tp.puzzle[p.Point.x * 3 + p.Point.y] = tp.puzzle[tp.Point.x * 3 + tp.Point.y];
				tp.puzzle[tp.Point.x * 3 + tp.Point.y] = 'x';//空白和移动的点交换数据
				cantorValue = Cantor(tp);//计算临时电的康拓值
				if (!visited[cantorValue]) {//未被访问过
					tp.g++;//g度加1
					tp.f = tp.g + h(tp);//拿到启发函数f 用于优先队列判断
					pq.push(tp);
					visited[cantorValue] = true;//当前标记访问
					path[cantorValue] = path[Cantor(p)] + positive_dir[i];//路径记录
					if (!cantorValue) {//到达终点情况
						result = path[0];
						return;
					}
				}
			}
			tp = p;//回溯p点位置
		}
	}
}
int main(void) {
	int CantorValue;
	for (int i = 0; i < N; i++) {
		EndPoint.puzzle[i] = i + 1 + '0';
	}
	EndPoint.puzzle[8] = 'x';
	EndPoint.Point.x = EndPoint.Point.y = 2;
	//初始化结束状态
	while (1) {
		while (!pq.empty()) {
			pq.pop();
		}//清空队列为下一次做准备
		for (int i = 0; i < N; i++) {
			cin >> StartPoint.puzzle[i];
			if (StartPoint.puzzle[i] == 'x') {
				StartPoint.Point.x = i / 3;
				StartPoint.Point.y = i % 3;
			}
		}//录入起始点的信息
		memset(visited, 0, sizeof(visited));//访问表初始化为0
		memset(path, 0, sizeof(path));//初始化路径表
		CantorValue = Cantor(StartPoint);//计算起始点康拓值
		path[CantorValue]  = "";
		if (!ISOK(StartPoint)) {
			cout << "unsolvable" << endl;
		}
		StartPoint.g = 0;
		StartPoint.f = StartPoint.g + h(StartPoint);
		pq.push(StartPoint);
		visited[CantorValue] = 1;//起始点标记访问
		Astar();
		cout << result << endl;//输出结果
	}
	system("pause");
	return 0;
}

利用pre和op来记录路径

# include<iostream>
# include<string>
# include<stack>
# include<queue>
#define N 9
#define NMAX 362885 
using namespace std;
struct p {
	int x, y;
};
class Puzzle {
public:
	char puzzle[N];
	p Point;//二维数组中的点
	int f, g;//启发函数 f=g+h
	Puzzle() { ; }
}StartPoint, EndPoint;
class cmp {
public:
	bool operator()(Puzzle a, Puzzle b) {
		return a.f > b.f;
	}
};
bool visited[NMAX];//判断是否访问
const int fac[] = { 1,1,2,6,24,120,720,5040,40320 };//康托序列   
int dri[4][2] = { { 0,1 },{ 1,0 },{ 0,-1 },{ -1,0 } };//东 南 西 北
char positive_dir[4] = { 'r','d','l','u' };//方向与dri东南西北对应右下左上
char op[NMAX];
int pre[NMAX];
stack<char>s;
priority_queue<Puzzle, vector<Puzzle>, cmp>pq;//定义一个优先队列
int abs(int a) {
	return a > 0 ? a : -a;
}
int h(Puzzle p) {//启发函数的曼哈顿距离
	int sum = 0;
	int c;
	for (int i = 0; i < N; i++) {
		if (p.puzzle[i] == 'x')continue;
		c = p.puzzle[i] - '0';
		if ( c!= i + 1) {
			sum += abs((c-1) / 3 - i / 3) + abs((c-1) % 3 - i % 3);
		}
	}
	return sum;
}
int Cantor(Puzzle p) {//计算康拓值用于hash判重
	int i, j, n, sum = 0;
	for (i = 0; i < N; i++) {
		n = 0;
		for (j = i + 1; j < N; j++) {
			if (p.puzzle[j] < p.puzzle[i])
				n++;
		}
		sum += fac[8 - i] * n;
	}
	return sum;
}
bool ISOK(Puzzle p) {//判定标准逆序奇偶性不变 (判断输入八数码是否有解)
	int i, j, n = 0;
	for (i = 0; i < N; i++) {
		if (p.puzzle[i] == 'x')	continue;
		for (j = i - 1; j >= 0; j--) {
			if (p.puzzle[j] == 'x')	continue;
			if (p.puzzle[j] < p.puzzle[i])
				n++;
		}
	}
	return n & 1 ? false : true;
}
void Astar(void) {
	Puzzle p, tp;
	int cantorValue;
	while (!pq.empty()) {
		tp = p = pq.top();
		pq.pop();
		for (int i = 0; i < 4; i++) {
			tp.Point.x += dri[i][0];
			tp.Point.y += dri[i][1];
			if ((tp.Point.x >= 0 && tp.Point.x < 3) && (tp.Point.y >= 0 && tp.Point.y < 3)) {//在范围内
				tp.puzzle[p.Point.x * 3 + p.Point.y] = tp.puzzle[tp.Point.x * 3 + tp.Point.y];
				tp.puzzle[tp.Point.x * 3 + tp.Point.y] = 'x';//空白和移动的点交换数据
				cantorValue = Cantor(tp);//计算临时电的康拓值
				if (!visited[cantorValue]) {//未被访问过
					tp.g++;//g度加1
					tp.f = tp.g + h(tp);//拿到启发函数f 用于优先队列判断
					pq.push(tp);
					visited[cantorValue] = true;//当前标记访问
					pre[cantorValue] = Cantor(p);
					op[cantorValue] = positive_dir[i];
					if (!cantorValue) return;
				}
			}
			tp = p;//回溯p点位置
		}
	}
}
int main(void) {
	int CantorValue;
	for (int i = 0; i < N; i++) {
		EndPoint.puzzle[i] = i + 1 + '0';
	}
	EndPoint.puzzle[8] = 'x';
	EndPoint.Point.x = EndPoint.Point.y = 2;
	//初始化结束状态
	while (1) {
		while (!pq.empty()) {
			pq.pop();
		}//清空队列为下一次做准备
		for (int i = 0; i < N; i++) {
			cin >> StartPoint.puzzle[i];
			if (StartPoint.puzzle[i] == 'x') {
				StartPoint.Point.x = i / 3;
				StartPoint.Point.y = i % 3;
			}
		}//录入起始点的信息
		memset(visited, 0, sizeof(visited));//访问表初始化为0
		CantorValue = Cantor(StartPoint);//计算起始点康拓值
		if (!ISOK(StartPoint)) {
			cout << "unsolvable" << endl;
		}
		StartPoint.g = 0;
		StartPoint.f =  h(StartPoint);
		pq.push(StartPoint);
		visited[CantorValue] = 1;//起始点标记访问
		Astar();
		int index;
		for (index = 0; index != CantorValue; index = pre[index])
			s.push(op[index]);
		while (!s.empty()) {
			cout << s.top();
			s.pop();
		}
	}
	system("pause");
	return 0;
}

思路4:IDAstar

猜你喜欢

转载自blog.csdn.net/QingCoffe/article/details/84706110