八數碼(雙向廣搜)


早上看了提到雙向廣搜的一篇文章,其中講了雙向廣搜可以節約一半的時間和一半的空間(理論上),我畫了一幅圖:

(上面的對應普通BFS,下面的對應雙向廣搜)

可以看出簡單BFS的搜索節點大約是雙向廣搜的二倍。

對於八數碼問題,由於逆序剪枝可以將所有無解的狀態全部剪掉,剩余的都是有解的狀態,所以使用雙向廣搜速度可能會更快;

對下面兩組數據(分別輸入)

1 2 3 4 5 6 7 8 0
8 7 6 5 4 3 2 1 0

2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2

正確輸出對應是 30、 31

使用BFS的運行時間:0.390s  0.359s

使用雙廣的運行時間:0.109s 0.046s

雙廣我開始想按自己的思路來寫,定義的是兩個隊列,兩個vis[],兩個dist[],最后發現根本運行不了(后來寫后面的時才發現是將幾個大的數組定義在了局部引起的),睡了一覺,看了這篇Poj 1915 - Knight Moves 雙向廣搜(Maxwell.O.Y),才對雙廣的結構有了新的理解;

  1 # include <stdio.h>
  2 # include <mem.h>
  3 
  4 # define MAXN (362880 + 5)
  5 
  6 typedef struct 
  7 {
  8     char a[9];
  9 }state;
 10 
 11 const int dir[4][2] = {{-1,0}, {0,1}, {1,0}, {0,-1}};
 12 int fact[9];
 13 
 14 int front, rear;
 15 state cur, nst;            /* new state */
 16 char vis[MAXN];
 17 char dist[MAXN];        /* 求的是最短距離( < 100),可以用 char 類型 */
 18 state Q[MAXN/2];
 19 
 20 
 21 void read(state *s);
 22 int inversions(state s);
 23 int cantor(state s);
 24 void init_fact(void);
 25 int bfs_d(state start, state goal);
 26 
 27 int main()
 28 {
 29     state start, goal;
 30     
 31     freopen("in.txt", "r", stdin);
 32     freopen("out.txt", "w", stdout);
 33     
 34     init_fact();
 35 
 36     read(&start);
 37     read(&goal);
 38 
 39     if (inversions(start)%2 == inversions(goal)%2)
 40     {
 41         printf("%d\n", bfs_d(start, goal));
 42     }
 43     else puts("-1");
 44 
 45     return 0;
 46 }
 47 
 48 int bfs_d(state start, state goal)
 49 {
 50     int i, x, y, nx, ny, ct, nt;
 51 
 52     memset(vis, 0, sizeof(vis));
 53     memset(dist, 0, sizeof(dist));
 54 
 55     front = 1;
 56     Q[front] = start;
 57     rear = 2;
 58     Q[rear++] = goal;
 59     vis[cantor(start)] = 1;                /* 1 表示從起始節點擴展得到 */
 60     vis[cantor(goal)] = 2;                /* 2 表示從目標節點擴展得到 */
 61 
 62     while (front < rear)
 63     {
 64         cur = Q[front++];
 65         ct = cantor(cur);
 66         for (i = 0; cur.a[i] && i < 9; ++i) ;
 67         x = i / 3;
 68         y = i % 3;
 69         for (i = 0; i < 4; ++i)
 70         {
 71             nx = x + dir[i][0];
 72             ny = y + dir[i][1];
 73             if (nx>=0 && nx<3 && ny>=0 && ny<3)
 74             {
 75                 nst = cur;
 76                 nst.a[x*3+y] = cur.a[nx*3+ny];
 77                 nst.a[nx*3+ny] = 0;
 78                 if (!vis[nt = cantor(nst)])
 79                 {
 80                     Q[rear++] = nst;
 81                     /* foot[nt] = ct; */
 82                     dist[nt] = dist[ct] + 1;
 83                     vis[nt] = vis[ct];
 84                 }
 85                 else if (vis[ct] != vis[nt])
 86                 {
 87                     return 1 + dist[nt] + dist[ct];
 88                 }
 89             }
 90         }
 91     }
 92 
 93     return -1;
 94 }
 95 
 96 void read(state *s)
 97 {
 98     int i;
 99     char c[5];
100 
101     for (i = 0; i < 9; ++i)
102     {
103         scanf("%s", c);
104         if (c[0] == 'x') (*s).a[i] = 0;
105         else (*s).a[i] = c[0] - '0';
106     }
107 }
108 
109 int inversions(state s)
110 {
111     char ch;
112     int i, j, ret;
113 
114     ret = 0;
115     for (i = 0; i < 9; ++i)
116     {
117         if (s.a[i] == 0) continue;
118         ch = s.a[i];
119         for (j = i+1; j < 9; ++j)
120         {
121             if (s.a[j] < ch && s.a[j] != 0)
122                 ++ret;
123         }
124     }
125 
126     return ret;
127 }
128 
129 int cantor(state s)
130 {
131     char ch;
132     int i, j, ret, cnt;
133 
134     ret = 0;
135     for (i = 0; i < 9; ++i)
136     {
137         cnt = 0;
138         ch = s.a[i];
139         for (j = i+1; j < 9; ++j)
140         {
141             if (s.a[j] < ch)
142                 ++cnt;
143         }
144         ret += cnt*fact[8-i];
145     }
146 
147     return ret;
148 }
149 
150 void init_fact(void)
151 {
152     int i;
153 
154     fact[0] = 1;
155     for (i = 1; i < 9; ++i)
156     {
157         fact[i] = i * fact[i-1];
158     }
159 }

用到的主要有:逆序剪枝,康托展開,雙向廣搜(……第四層)。

********************************************

剛吃飯想到了一種比較不正常的提速方法,也是看了郭大俠的博客才考試考慮提速的:假設最短距離的最大值是31,相應擴展的節點為N的話,根據BFS的特點完全可以將隊列縮小很多!試了試果然提速了不少:(對應上面的例子)15ms 31ms;

郭大俠提出了一種構造法(非最優解)可以在有限的步數內按照一定的規則構造出一個解,對於大多數輸入,速度要比搜索要快一些。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM