写之前拜读了这篇文章:八数码的八境界
个人觉得写顺序为
一(可写可不写,介意找工作的的人最好试试这种写法)-->三 -->二 -->四 -> 六-->八
境界一、逆向广搜+STL
多组输入输出,可以想到打表,bfs时间复杂度为9!,查询复杂度为O(1)
判重方法:
setvis;
set红黑树实现,查找效率log(n);总的效率nlog(n)(10^6),肯定会TLE
#include
#include
#include
#include
#include
这份代码由于将path放在node里面,也会MLE
境界三、逆向广搜+哈希+打表
是对境界一的改进,set判重改进为cantor判重
从目标状态123456780反向搜索,记录所有可达状态的路径
用了两种记录路径的方法,一种是用char path[maxn][36],每个节点维护一个len变量;第二种是每个状态记录上一个状态parent,
path[cur.status].from=parent.stauts
上面是第二种方法,下面是第一种方法
代码如下
//char path[maxn][42]
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 362880 + 5;
int fac[9];
char path[maxn][42];
int vis[maxn];
int dx[] = { -1, 1, 0, 0 };
int dy[] = { 0, 0, -1, 1 };
char dir[]="durl";
struct node
{
public:
node(int p, int s, int l) :pos(p), status(s), len(l){}
public:
int pos;
int status;
int len;
};
void calFac(int *f, int n)
{
f[0] = 1;
for (int i = 1; i2 || y>2)return false;
else return true;
}
void bfs()
{
queueq;
int s[] = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };
node n(8, encodeCantor(s),0);
vis[n.status] = 1;
path[n.status][0] = '\0';
q.push(n);
while (!q.empty()){
n = q.front();
q.pop();
int x = n.pos / 3; int y = n.pos % 3;
for (int i = 0; i<4; ++i){
int newx = x + dx[i];
int newy = y + dy[i];
if (!check(newx, newy))continue;
int newpos = newx * 3 + newy;
decodeCantor(s, n.status);
swap(s[n.pos], s[newpos]);
int nstatus = encodeCantor(s);
if (!vis[nstatus]){
for (int i = 0; i < n.len; i++)path[nstatus][i] = path[n.status][i];
path[nstatus][n.len] = dir[i];
path[nstatus][n.len+1] = '\0';
vis[nstatus] = 1;
q.push(node(newpos, nstatus,n.len+1));
}
swap(s[n.pos], s[newpos]);
}
}
}
void init()
{
calFac(fac, 9);
memset(vis, 0, sizeof(vis));
//cout<> tmp){
if (tmp[0] == 'x')in[0] = 0;
else in[0] = tmp[0] - '0';
for (int i = 1; i<9; ++i){
cin >> tmp;
if (tmp[0] == 'x')in[i] = 0;
else in[i] = tmp[0] - '0';
}
int status = encodeCantor(in);
if (!vis[status])cout << "unsolvable" << endl;
else {
int len = strlen(path[status]);
for (int i = len - 1; i >= 0; --i)printf("%c", path[status][i]);
printf("\n");
}
}
return 0;
}
第二种记录路径的方法,速度更快,内存更小
//记录上一个stauts
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 362880 + 5;
int fac[9];
int vis[maxn];
int dx[] = { -1, 1, 0, 0 };
int dy[] = { 0, 0, -1, 1 };
char dir[]="durl";
struct node
{
public:
node(int p, int s) :pos(p), status(s){}
public:
int pos;
int status;
};
struct path
{
public:
int from, dir;
}p[maxn];
void calFac(int *f, int n)
{
f[0] = 1;
for (int i = 1; i2 || y>2)return false;
else return true;
}
void bfs()
{
queueq;
int s[] = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };
node n(8, encodeCantor(s));
vis[n.status] = 1;
p[n.status].from = -1;
q.push(n);
while (!q.empty()){
n = q.front();
q.pop();
int x = n.pos / 3; int y = n.pos % 3;
for (int i = 0; i<4; ++i){
int newx = x + dx[i];
int newy = y + dy[i];
if (!check(newx, newy))continue;
int newpos = newx * 3 + newy;
decodeCantor(s, n.status);
swap(s[n.pos], s[newpos]);
int nstatus = encodeCantor(s);
if (!vis[nstatus]){
p[nstatus].from = n.status;
p[nstatus].dir = i;
vis[nstatus] = 1;
q.push(node(newpos, nstatus));
}
swap(s[n.pos], s[newpos]);
}
}
}
void print(int s)
{
while (p[s].from != -1)
{
printf("%c", dir[p[s].dir]);
s = p[s].from;
}
printf("\n");
}
void init()
{
calFac(fac, 9);
memset(vis, 0, sizeof(vis));
}
int main()
{
//freopen("in.txt", "r", stdin);
init();
bfs();
string tmp;
int in[9];
while (cin >> tmp){
if (tmp[0] == 'x')in[0] = 0;
else in[0] = tmp[0] - '0';
for (int i = 1; i<9; ++i){
cin >> tmp;
if (tmp[0] == 'x')in[i] = 0;
else in[i] = tmp[0] - '0';
}
int status = encodeCantor(in);
if (!vis[status])cout << "unsolvable" << endl;
else {
print(status);
}
}
return 0;
}
境界四、双向BFS+康托判重+剪枝
图转自http://www.cnblogs.com/JMDWQ/archive/2012/05/20/2510698.html
如图,双向bfs可以节约一半的时间和空间,注意境界一复杂度时9!,现在是9!/2,还不够快。
故注意进行奇偶剪枝,否则会TLE
剪枝原理:
逆序数:对于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
a)若 x5 在6 和x7之间,则逆序数不变
通过以上分析可知:只有起始状态可终止状态逆序数奇偶性相同才能转换
双向bfs,采用两个队列q1和q2,一个从起始状态向目标状态扩展,另一个从目标状态像起始状态扩展
判别重可以采用
int vis[manx];//q1为1,q2为2
//可以用
bool vis1[maxn],vis2[maxn];
输出路径时
q1的另用一个数组记录下来
char tmp[36]; int len = 0;
while (p1[s]!= -1){
tmp[len++] = dir1[d1[s]];
s = p1[s];
}
q2利用和境界三一样的方法
#include
#include
#include
using namespace std;
const int maxn = 362880 + 5;
char *dir1 = "udlr";
char *dir2 = "durl";
int dx[] = { -1, 1, 0, 0 };
int dy[] = { 0, 0, -1, 1 };
struct node
{
public:
int status, pos;
};
int p1[maxn], p2[maxn];
int d1[maxn], d2[maxn];
bool vis1[maxn],vis2[maxn];
int fac[9];
void init()
{
fac[0] = 1;
for (int i = 1; i<9; ++i){
fac[i] = i*fac[i - 1];
}
}
int encodeCantor(int *s)
{
int _rank, sum = 0;
for (int i = 0; i < 9; ++i){
_rank = 0;
for (int j = i + 1; j<9; ++j){
if (s[i]>s[j])_rank++;
}
sum += fac[8 - i] * _rank;
}
return sum;
}
void decodeCantor(int *arr, int s)
{
bool flag[9];
memset(flag, 0, sizeof(flag));
for (int i = 0; i<9; ++i){
int _rank = s / fac[8 - i];
for (int j = 0; j <= _rank; ++j){
if (flag[j])_rank++;
}
arr[i] = _rank;
flag[_rank] = 1;
s = s%fac[8 - i];
}
}
bool check(int x, int y)
{
if (x<0 || y<0 || x>2 || y>2)return false;
else return true;
}
void print(int status)
{
int s = status;
char tmp[36]; int len = 0;
while (p1[s]!= -1){
tmp[len++] = dir1[d1[s]];
s = p1[s];
}
s = status;
for (int i = len - 1; i >= 0; i--)printf("%c", tmp[i]);
while (p2[s] != -1){
printf("%c", dir2[d2[s]]);
s = p2[s];
}
printf("\n");
}
void bfs(int init_pos, int init_status)
{
queueq1, q2;
memset(vis1, 0, sizeof(vis1));
memset(vis2, 0, sizeof(vis2));
int arr[9];
int aim[] = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };
int aim_s = encodeCantor(aim);
node n;
n.status=init_status;
n.pos=init_pos;
q1.push(n);
n.status=aim_s;
n.pos=8;
q2.push(n);
node cur;
node next;
p1[init_status] = -1;
p2[aim_s] = -1;
vis1[init_status] = 1;
vis2[aim_s] = 1;
int flag;
while (!q1.empty() || !q2.empty()){
if (!q1.empty() && (q2.empty() || q1.size() <= q2.size())) {//选择较少的扩展,效率较高
flag = 1;
cur = q1.front();
q1.pop();
if (vis2[cur.status]){
print(cur.status);
return;
}
}
else {
flag = 2;
cur = q2.front();
q2.pop();
if (vis1[cur.status]){
print(cur.status);
return;
}
}
decodeCantor(arr, cur.status);
int x = cur.pos / 3; int y = cur.pos % 3;
for (int i = 0; i<4; ++i){
int nx = x + dx[i]; int ny = y + dy[i];
if (!check(nx, ny))continue;
int newpos = nx * 3 + ny;
swap(arr[cur.pos], arr[newpos]);
int nstatus = encodeCantor(arr);
if (flag == 1 && !vis1[nstatus]){
p1[nstatus] = cur.status;
d1[nstatus] = i;
if (vis2[nstatus]){
print(nstatus);
return;
}
else {
vis1[nstatus] = 1;
next.status=nstatus;
next.pos=newpos;
q1.push(next);
}
}
else if (flag == 2 && !vis2[nstatus]){
p2[nstatus]= cur.status;
d2[nstatus] = i;
if (vis1[nstatus]){
print(nstatus);
return;
}
else{
vis2[nstatus] = 1;
next.status=nstatus;
next.pos=newpos;
q2.push(next);
}
}
swap(arr[cur.pos], arr[newpos]);
}
}
return;
}
//奇偶剪枝
bool pruning(int *arr){
int flag = 0;
for (int i = 0; i<9; ++i){
if (arr[i] == 0) continue;
for (int j = i + 1; j<9; ++j){
if (arr[j] && arr[i]>arr[j]) flag++;
}
}
if (flag % 2) return true;
return false;
}
int main()
{
//freopen("in.txt", "r", stdin);
char tmp[3];
int in[9];
int pos;
init();
while (scanf("%s", tmp) != EOF){
if (tmp[0] == 'x'){
in[0] = 0;
pos = 0;
}
else in[0] = tmp[0] - '0';
for (int i = 1; i<9; ++i){
scanf("%s", tmp);
if (tmp[0] == 'x'){
in[i] = 0;
pos = i;
}
else in[i] = tmp[0] - '0';
}
int status = encodeCantor(in);
if (pruning(in))printf("unsolvable\n");
else {
bfs(pos, status);
}
}
return 0;
}
境界六、A*+cantor判重+曼哈顿距离
境界五和境界六差不多,就是启发式函数使用的不一样,直接写了境界六
参考理解A*寻路算法具体过程
启发式算法的估价函数为 f(n) = g(n) + h(n)
G 表示从起点 A 移动到网格上指定方格的移动耗费 (可沿斜方向移动).
H 表示从指定的方格移动到终点 B 的预计耗费 (H 有很多计算方法, 这里我们设定只可以上下左右移动).
A*算法的估价函数可以表示为
f'(n) = g'(n) + h'(n)
f’(n)是估价函数,g’(n)是起点到终点的最短路径值,h’(n)是n到目标的最断路经的启发值。由 于这个f’(n)其实是无法预先知道的,所以我们用估价函数f(n)做近似。g(n)代替g’(n),但 g(n)>=g’(n) 才可(大多数情况下都是满足的,可以不用考虑),h(n)代替h’(n),但h(n)<=h’(n)才可(这一点特别的重 要)。
这里的G指的是bfs扩展的层数,因为一层就是一步
H指的是当前位置到终点的曼哈顿距离
计算曼哈顿距离的函数为
int getH(int *arr)
{
int x, y;
int h = 0;
for (int i = 0; i<9; ++i){
if (arr[i]){
x = i / 3;
y = i % 3;
h += abs(x - tx[arr[i]]) + abs(y - ty[arr[i]]);//tx,ty分别表示arr[i]的目标位置坐标
}
}
return h;
}
上代码
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 362880 + 5;
int fac[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320 };
char *dir = "udlr";
int dx[] = { -1, 1, 0, 0 };
int dy[] = { 0, 0, -1, 1 };
bool vis[maxn];
struct node
{
int status, pos;
int g, h;
bool operator<(const node&n)const{
return (g + h)>(n.g + n.h);
}
};
struct path
{
int from, dir;
}p[maxn];
int tx[] = { 2, 0, 0, 0, 1, 1, 1, 2, 2 };
int ty[] = { 2, 0, 1, 2, 0, 1, 2, 0, 1 };
int encodeCantor(int *arr)
{
int sum = 0;
for (int i = 0; i<9; ++i){
int _rank = 0;
for (int j = i + 1; j<9; ++j){
if (arr[i]>arr[j])_rank++;
}
sum += _rank*fac[8 - i];
}
return sum;
}
void decodeCantor(int *arr, int val)
{
int flag[9], _rank;
memset(flag, 0, sizeof(flag));
for (int i = 0; i<9; ++i){
_rank = val / fac[8 - i];
for (int j = 0; j <= _rank; ++j){
if (flag[j])_rank++;
}
arr[i] = _rank;
flag[_rank] = 1;
val = val%fac[8 - i];
}
}
int getH(int *arr)
{
int x, y;
int h = 0;
for (int i = 0; i<9; ++i){
if (arr[i]){
x = i / 3;
y = i % 3;
h += abs(x - tx[arr[i]]) + abs(y - ty[arr[i]]);
}
}
return h;
}
bool pruning(int *arr)
{
int sum = 0;
for (int i = 0; i<9; ++i){
if (!arr[i])continue;
for (int j = i + 1; j<9; ++j){
if (arr[j]&&arr[i]>arr[j])sum++;
}
}
if (sum % 2)return true;
else return false;
}
bool check(int x, int y)
{
if (x<0 || y<0 || x>2 || y>2)return false;
else return true;
}
void A_star(int *arr)
{
int aim[] = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };
int aim_s = encodeCantor(aim);
priority_queueq;
memset(vis, 0, sizeof(vis));
node cur, next;
int status = encodeCantor(arr);
for (int i = 0; i<9; ++i){
if (!arr[i]){
cur.pos = i;
break;
}
}
cur.status = status;
cur.g = 0;
cur.h = getH(arr);
q.push(cur);
p[status].from = -1;
vis[status] = 1;
while (!q.empty()){
cur = q.top();
q.pop();
decodeCantor(arr, cur.status);
if (cur.status == aim_s){
int s = cur.status, len = 0;
char res[maxn];
while (p[s].from != -1){
res[len++] = dir[p[s].dir];
s = p[s].from;
}
for (int i = len - 1; i >= 0; i--){
printf("%c", res[i]);
}
printf("\n");
return;
}
else{
int x = cur.pos / 3; int y = cur.pos % 3;
for (int i = 0; i<4; ++i){
int nx = x + dx[i];
int ny = y + dy[i];
if (!check(nx, ny))continue;
int newpos = nx * 3 + ny;
swap(arr[newpos], arr[cur.pos]);
int nstatus = encodeCantor(arr);
if (!vis[nstatus]){
next.status = nstatus;
next.pos = newpos;
next.g = cur.g + 1;
next.h = getH(arr);
p[next.status].from = cur.status;
p[next.status].dir = i;
vis[next.status] = 1;
q.push(next);
}
swap(arr[newpos], arr[cur.pos]);
}
}
}
printf("unsolvable\n");
}
int main()
{
//freopen("in.txt", "r", stdin);
char tmp[5];
int in[9];
while (scanf("%s", tmp) != EOF){
if (tmp[0] == 'x')in[0] = 0;
else in[0] = tmp[0] - '0';
for (int i = 1; i<9; ++i){
scanf("%s", tmp);
if (tmp[0] == 'x')in[i] = 0;
else in[i] = tmp[0] - '0';
}
//int status=encodeCantor(in);
if (pruning(in))printf("unsolvable\n");
else A_star(in);
}
return 0;
}