八数码(双向广搜)

早上看了提到双向广搜的一篇文章,其中讲了双向广搜可以节约一半的时间和一半的空间(理论上),我画了一幅图:

(上面的对应普通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;

郭大侠提出了一种构造法(非最优解)可以在有限的步数内按照一定的规则构造出一个解,对于大多数输入,速度要比搜索要快一些。

    原文作者:BFS
    原文地址: https://www.cnblogs.com/JMDWQ/archive/2012/05/20/2510698.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞