hdu1043八数码 bfs 打表/双向bfs/A*+康托判重+逆序奇偶剪枝

写之前拜读了这篇文章:八数码的八境界

个人觉得写顺序为

一(可写可不写,介意找工作的的人最好试试这种写法)-->三 -->二 -->四 -> 六-->八

境界一、逆向广搜+STL

多组输入输出,可以想到打表,bfs时间复杂度为9!,查询复杂度为O(1)

判重方法:

set<node>vis;
set红黑树实现,查找效率log(n);总的效率nlog(n)(10^6),肯定会TLE

#include <iostream>
#include <cstdio>
#include <vector>
#include <set>
#include <map>
#include <string>
#include <queue>
using namespace std;

struct node{
public:
    node(string s, string pa, int p):sty(s),path(pa),pos(p){}
public:
    string sty;
    string path;
    int pos;
};
map<string,string>res;
set<string>vis;
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
string dir("durl");//ÄæÏòËÑË÷

bool check(int x,int y)
{
    if(x<0||x>2||y<0||y>2)return false;
    else return true;
}

void bfs(){
    queue<node>q;
    node n("12345678x","",8);
    vis.insert("12345678x");
    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;
            swap(n.sty[n.pos],n.sty[newpos]);
            if(vis.find(n.sty)!=vis.end()){
                swap(n.sty[n.pos],n.sty[newpos]);
                continue;
            }
            else{
                q.push(node(n.sty,dir[i]+n.path,newpos));
                vis.insert(n.sty);
                res[n.sty]=dir[i]+n.path;
                swap(n.sty[n.pos],n.sty[newpos]);
            }
        }
    }

}

int main()
{
    //freopen("in.txt","r",stdin);
    string in;
    string tmp;
    int spos;
    res[in]="";
    bfs();
    while(cin>>tmp){
        in.push_back(tmp[0]);
        for(int i=1;i<9;++i){
            cin>>tmp;
            in.push_back(tmp[0]);
        }
        //cout<<in<<endl;
        if(res[in]=="")printf("unsolvable\n");
        else cout<<res[in]<<endl;
    }

    return 0;
}


这份代码由于将path放在node里面,也会MLE

境界三、逆向广搜+哈希+打表

     是对境界一的改进,set判重改进为cantor判重

     从目标状态123456780反向搜索,记录所有可达状态的路径

     用了两种记录路径的方法,一种是用char path[maxn][36],每个节点维护一个len变量;第二种是每个状态记录上一个状态parent,

path[cur.status].from=parent.stauts
上面是第二种方法,下面是第一种方法

代码如下

//char path[maxn][42]

#include <iostream>
#include <cstdio>
#include <memory.h>
#include <queue>
#include <string>
#include <cstring>

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; i<n; ++i)f[i] = f[i - 1] * i;
}
int encodeCantor(int *s)
{
    int i, j, num, temp;
    num = 0;
    for (i = 0; i<9; ++i){
        temp = 0;
        for (j = i + 1; j<9; ++j){
            if (s[j]<s[i])
                temp++;
        }
        num += fac[9 - 1 - i] * temp;
    }
    return num;
}

void decodeCantor(int *s, int val)
{
    bool flag[10];
    memset(flag, 0, sizeof(flag));
    for (int i = 0; i<9; ++i){
        int _rank = val / fac[8 - i];
        for (int j = 0; j <= _rank; ++j){
            if (flag[j]){
                _rank++;
            }
        }
        s[i] = _rank;
        flag[_rank] = true;;
        val = val%fac[8 - i];
    }
}

bool check(int x, int y)
{
    if (x<0 || y<0 || x>2 || y>2)return false;
    else return true;
}

void bfs()
{
    queue<node>q;
    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<<fac[8]*9<<endl;
}

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 {
            int len = strlen(path[status]);
            for (int i = len - 1; i >= 0; --i)printf("%c", path[status][i]);
            printf("\n");
        }
    }

    return 0;
}


第二种记录路径的方法,速度更快,内存更小

//记录上一个stauts

#include <iostream>
#include <cstdio>
#include <memory.h>
#include <queue>
#include <string>
#include <cstring>

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; i<n; ++i)f[i] = f[i - 1] * i;
}
int encodeCantor(int *s)
{
    int i, j, num, temp;
    num = 0;
    for (i = 0; i<9; ++i){
        temp = 0;
        for (j = i + 1; j<9; ++j){
            if (s[j]<s[i])
                temp++;
        }
        num += fac[9 - 1 - i] * temp;
    }
    return num;
}

void decodeCantor(int *s, int val)
{
    bool flag[10];
    memset(flag, 0, sizeof(flag));
    for (int i = 0; i<9; ++i){
        int _rank = val / fac[8 - i];
        for (int j = 0; j <= _rank; ++j){
            if (flag[j]){
                _rank++;
            }
        }
        s[i] = _rank;
        flag[_rank] = true;;
        val = val%fac[8 - i];
    }
}

bool check(int x, int y)
{
    if (x<0 || y<0 || x>2 || y>2)return false;
    else return true;
}


void bfs()
{
    queue<node>q;
    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 <x6 && x5<x7,则逆序数-2

     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 <stdio.h>
#include <queue>
#include <memory.h>
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)
{
    queue<node>q1, 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 <iostream>
#include <stdio.h>
#include <queue>
#include <memory.h>
#include <stdlib.h>
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_queue<node>q;
    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;
}





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值