cdoj 414 八数码 (双向bfs+康拓展开,A*)

 一道关乎人生完整的问题。

DBFS的优越:避免了结点膨胀太多。

假设一个状态结点可以扩展m个子结点,为了简单起见,假设每个结点的扩展都是相互独立的。

分析:起始状态结点数为1,每加深一层,结点数An = An-1*m。假如搜索了i层找到终点,那么经过的结点数是O(i^m),如果从两边同时搜索,结点数是O(i^(m/2))。

极端情况,终点完全封闭。

 

 

DBFS的正确姿势:

图片来源:http://www.cppblog.com/Yuan/archive/2011/02/23/140553.aspx

下面是原博客上的分析:

交替结点可能会因为扩展顺序而认为s-1-5-3-t是最短路。//这也可能得到正确结果,与结点的扩展顺序有关系

然而交替层次的做法才是正确的。

优化:提供速度的关键在于使状态扩展得少一些,所以优先选择队列长度较少的去扩展,保持两边队列长度平衡。这比较适合于两边的扩展情况不同时,一边扩展得快,一边扩展得慢。如果两边扩展情况一样时,加了后效果不大,不过加了也没事。

 

 ----------------------------------分割线------------------------------------------------------

DBFS的代码也是磕了好久想到怎么实现的。

加了奇偶剪枝和一些小优化

 

更新。经过仔细思考,简化了代码。

/*
 Created by Rey Chen on 2015.7.5
 */

#include<bits/stdc++.h>
using namespace std;

//#define local
const int maxn = 362880;
int vis1[maxn];
int vis2[maxn];//保存距离起点的距离,初始值-1
int fac[9];

struct node
{
    int e[9];
    int p;
    int cod;//避免二次计算,初始值为-1
    int code() {
        if(~cod) return cod;
        int Hash = 0;
        for(int i = 0; i < 9; i++) {
            int cnt = 0;
            for(int j = i+1; j < 9; j++)
                if(e[j] < e[i]) cnt++;
            Hash += fac[8-i] * cnt;
        }
        return cod = Hash;
    }
    int rev_value(){//用于奇偶剪枝
        int res = 0, cnt ,i ,j;
        for(i = 0; i < 9; i++) {
            if(e[i]) {
                cnt = 0;
                for(j = i+1; j < 9; j++)
                    if(e[j] && e[j] < e[i]) cnt++;
                res += cnt;
            }
        }
        return res;
    }
};

node start;
node ed;
int edHash;
int nodesz;
typedef vector<node>* vnodep;
vector<node> v1;
vector<node> v2;
vector<node> v3;
vector<node>:: iterator it,tmp_it;

const int dx[] = {-1, 1, 0, 0};
const int dy[] = { 0, 0,-1, 1};
bool ilegal[9][4];
void solve()
{
    if(start.rev_value()&1) { puts("unsolvable"); return; }//忽略0,操作不会改变逆序数总数的奇偶性。
    int t;
    t = start.code();
    if(t == edHash) { puts("0");return;}
    memset(vis1,-1,sizeof(vis1) );
    memset(vis2,-1,sizeof(vis2) );
    vis1[t] = 0;
    vis2[edHash] = 0;

    v1.clear(); v2.clear(); v3.clear();
    vnodep q1 = &v1, q2 = &v2, nxt = &v3;
    q1->push_back(start);
    q2->push_back(ed);
    int *V1 = vis1, *V2 = vis2;
    while( !q1->empty() && !q2->empty() ) {
        if(q1->size() > q2->size()) swap(q1,q2),swap(V1,V2); //化简代码的小技巧
        for(it = q1->begin(), tmp_it = q1->end(); it != tmp_it ; it++){
            node& u = *it;
            node v;
            for(int i = 0;i < 4;i++){
                if(ilegal[u.p][i]) continue;
                int np = u.p + dx[i]*3 + dy[i];
                memcpy(&v,&u,nodesz); v.cod = -1;//memcpy 比直接赋值要快
                swap(v.e[np],v.e[u.p]);
                if(!~V1[t = v.code()]){
                    V1[t] = V1[u.code()] + 1;
                    if(~V2[t]){  printf("%d\n",V2[t]+V1[t]);  return;  }
                    v.p = np;
                    nxt->push_back(v);
                }
            }
        }
        q1->clear();
        swap(q1,nxt);
    }
    puts("unsolvable");
}

void init(){
    fac[0] = 1;
    for(int i = 1; i < 9; i++)
        fac[i] = fac[i-1]*i;
     for(int i = 0; i < 3; i++)
     for(int j = 0; j < 3; j++){
        for(int k = 0; k < 4; k++)
            if( (i == 0&& k == 0) || (i == 2&& k == 1) || (j == 0&& k == 2) || (j == 2&& k == 3) )
                ilegal[i*3+j][k] = true;
            else ilegal[i*3+j][k] = false;
     }
}

int main()
{
#ifdef local
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif // local
    char ch,s[24];
    init();
    for(int i = 0; i < 8;i ++)
        ed.e[i] = i+1;
    ed.e[8] = 0;
    ed.p = 8;
    ed.cod = -1;
    edHash = ed.code();
    nodesz = sizeof(ed);


    while(gets(s)) {
        int j = 0;
        for(int i = 0; i < 9; i ++, j++) {
            while(sscanf(s+j,"%c",&ch),ch == ' ')j++;
            if(ch == 'x'){
                start.e[i] = 0; start.p = i;
            }else {
                start.e[i] = ch - '0';
            }
        }
        start.cod = -1;
        solve();
    }
    return 0;
}
View Code

 花了点时间写了A*。A*的关键在于估价函数,估价函数必须要小于实际值,越接近越好。

这里取的是除去x以后的曼哈顿距离。

这题为什么不需要两个表?需要open表是因为有可能有捷径的出现,这题不需要。

#include<bits/stdc++.h>
using namespace std;

int t[9],s[9],Zero,tHashCode;
int fac[9];
struct node
{
    int p[9], z, f, dist, hashCode;
    bool operator < (const node& rhs) const {
       return f > rhs.f || (f == rhs.f && dist > rhs.dist);
    }
};

inline int Hash(int *a)
{
    int ans = 0;
    for(int i = 0; i < 8; i++) {
        int cnt = 0;
        for(int j = i+1; j < 9; j++) if(a[j] < a[i]) cnt++;
        ans += fac[8-i] * cnt;
    }
    return ans;
}

inline int Rev_value(int *a){
    int ans = 0;
    for(int i = 0; i < 8; i++) {
        int cnt = 0;
        for(int j = i+1; j < 9; j++) if(a[j] && a[j] < a[i]) cnt++;
        ans += cnt;
    }
    return ans;
}
int Cost[9][9];

//除去x之外到目标的网格距离和
//x 和 其他数交换,理想情况每次距离减一
inline void Manhattan(node &A)
{
   A.f = A.dist;
    for(int i = 0; i < 9; i++)if(A.p[i])
        A.f += Cost[i][A.p[i]-1];
}

bool vis[362880];
const int dx[] = {-1, 1, 0, 0};
const int dy[] = { 0, 0,-1, 1};
int dz[9];
bool ilegal[9][4];

void AstarBfs()
{
    if(Rev_value(s)&1) { puts("unsolvable"); return; }
    node u;
    u.hashCode = Hash(s);
    if(u.hashCode == tHashCode) {puts("0"); return;}
    memset(vis,0,sizeof(vis));
    vis[u.hashCode] = 1;
    memcpy(u.p,s,sizeof(s));
    u.dist = 0;
    u.z = Zero;
    priority_queue<node> q;
    Manhattan(u);
    q.push(u);
    while(q.size()) {
        u = q.top(); q.pop();
        if(u.hashCode == tHashCode) {printf("%d\n",u.dist);return;}
        node v;
        for(int i = 0; i < 4; i++) {
            if(ilegal[u.z][i]) continue;
            v.z = u.z + dz[i];
            memcpy(v.p,u.p,sizeof(u.p));
            swap(v.p[v.z],v.p[u.z]);
            v.hashCode = Hash(v.p);
            if(vis[v.hashCode]) continue;
            vis[v.hashCode] = 1;
            v.dist = u.dist +1;
            Manhattan(v);
            q.push(v);
        }
    }
  puts("unsolvable");
}

void init()
{
    for(int i = 0; i < 9; i++)
        for(int j = 0; j < 9; j++)
        Cost[i][j] = (abs(i/3-j/3) + abs(i%3-j%3));
    for(int i = 0; i < 8; i++)
        t[i] = i+1;
    t[8] = 0;
    fac[0] = 1;
    for(int i = 1; i < 9; i++)
        fac[i] = fac[i-1]*i;
    tHashCode = Hash(t);
    for(int i = 0; i < 3; i++)
    for(int j = 0; j < 3; j++)
        for(int k = 0; k < 4; k++){
            int nx = i+dx[k], ny = j + dy[k];
            ilegal[i*3+j][k] = !(nx>=0 && nx < 3 && ny >= 0 && ny < 3);
        }
    for(int k = 0; k < 4; k++) dz[k] = dx[k]*3 + dy[k];
}


int main()
{
    init();
    char str[20];
    while(fgets(str,20,stdin)) {
        int j = 0;
        for(int i = 0; i < 9; i++, j++){
            char ch;
            while(sscanf(str+j,"%c",&ch),ch == ' ')j++;
            if(ch == 'x'){
                s[i] = 0;Zero = i;
            }else {
                s[i] = ch - '0';
            }
        }
        AstarBfs();
    }
    return 0;
}
View Code

 

附上数据生成器

#include<bits/stdc++.h>
using namespace std;

int main()
{
    srand( time( NULL ) );
    char s[20] ;
    char ori[20] = "12345678";
    int n = 10;
    int m = 10;
    int init = 1;
    for(int i = 0; i < 50; i++) next_permutation(ori,ori+8);
        for(int i=0;i<n;i++)
        {
            for(int j = 0;j<m;j++){
                strcpy(s,ori);
                s[8] = 'x';s[9] = '\0';
                swap(s[8],s[rand()%8]);
                for(int k = 0;k < 9; k++)
                    printf("%c%c",s[k],k==8?'\n':' ');
            }
            next_permutation(ori,ori+8);
        }

}
View Code

.bat

:loop
make.exe>data.txt
std.exe<data.txt>std.txt
my<data.txt>my.txt
fc my.txt std.txt
if not errorlevel 1 goto loop
pause

 

posted @ 2015-07-05 11:27  陈瑞宇  阅读(321)  评论(0编辑  收藏  举报