A*算法解决八数码问题

八数码问题

八数码问题(重排九宫问题)是在一个3 x 3的方格盘上, 放有1 ~ 8的数码,另一格为空(也可以定义为0)。空格四周的数码可以移到空格。要解决的问题是如何找到一个数码移动序列使初始的无序数码转变为特殊的序列。
例:
在这里插入图片描述

A*算法(启发式搜索)

在一个搜索图中,每下一步我们需要从所有叶节点中选择一个节点扩展。为了尽快找到从初始节点到目标节点的一条耗散值比较小的路径,我们希望所选择的节点尽可能在最佳路径上。

A算法给出了评价函数的定义: f(n) = g(n) + h(n)
其中,n为待评价的节点;g(n)为从初始节点s到节点n的最佳路径耗散值的估计值;h(n)为从节点n到目标节点t的最佳路径耗散值的估计值,称为启发函数;f(n)为从初始节点s经过节点n到达目标节点的最佳路径耗散值的估计值,成为评价函数,我们每次从叶节点选出f(n)最小的节点扩展。

如果启发函数满足 h(n) <= h*(n), 则可以证明当问题有解时,A算法一定可以得到一个耗散值最小的结果。满足该条件的A算法成为A*算法

解决八数码问题

在八数码问题中,g(n)可以定义为从初始节点走到当前节点时走过的步数,h(n)可以定义为不在位的将牌数,例如在上面的例子中,不在位的将牌数为7

以下为八数码扩展的过程以及每一步的耗散值(g(n) + h(n))

在这里插入图片描述

算法具体实现

0、设置一个变量OPEN用于存放那些搜索图上的叶节点,也就是已经被生成出来,但是还没被扩展的节点;变量CLOSE用于存放图中的非叶节点,也就是不但被生成出来,还已经被扩展的节点。
1、OPEN中的节点按照 f 值从小到大排列。每次从OPEN表中取出第一个元素 n 进行扩展,如果 n 是目标节点,则算法找到一个解,算法结束,否则扩展 n
2、对于 n 的子节点 m ,如果 m 既不在OPEN也不在CLOSE, 则将 m 加入OPEN; 如果 m 在CLOSE, 说明从初始节点到 m 有两条路径, 如果新路径耗散值大,什么都不做;如果较小,则将 m (新找到的)从CLOSE中取出放入OPEN中(删除原来在CLOSE的,将新找到的放入OPEN)
3、重复 1 ,知道找到一个解结束;或者OPEN为空算法以失败结束,说明无解

C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
#include <iostream>
using namespace std;

#define MAXLISTSIZE 10000
#define MAXSTEPSIZE 100

/*
定义八数码节点结构体
status存储节点的状态(即八数码的排列),G存储走的是第几步,H存储不在位的将牌数,F存储总耗散值,Zero存储‘0’将牌所在位置,
step存储该节点是上一节点通过怎样的移动得到的(1左2右3上4下)
*/
struct ENode
{
    int status[9];
    int G;
    int H;
    int F;
    int Zero;
    int step;
    ENode *Parent;
};

//最终状态
int FinalStatus[9] = { 1, 2, 3, 8, 0, 4, 7, 6, 5 };

//定义OPEN表和CLOSE表,open和close是表中最后一个内容的下一位序号
ENode OPEN[MAXLISTSIZE];
ENode CLOSE[MAXLISTSIZE];
int open = 0;
int close = 0;

ENode *Node;

/*
计算不在位的将牌数H
返回 H
*/
int CountH(int *status)
{
    int H = 0;
    int i;
    for (i = 0; i <= 8; i++)
    {
        if (FinalStatus[i] != status[i])
        {
            H++;
        }
    }
    return H;
}

/*
判断新生成的节点是否已经存在于OPEN表或CLOSE表中
返回 表征是否存在于OPEN或CLOSE的值,值为0 均不在,值>0 只在OPEN表,值<0 只在CLOSE表,|值|-1表示所在列表中的位置
*/
int Exist(ENode *N)
{
    int i, j;
    int H = 0;                      //计算不在位的将牌数,如果为0,则证明给函数的节点在表中已存在
    int status[9];

    Node = new ENode;
    Node = N;

    for (i = 0; i <= 8; i++)
    {
        status[i] = Node->status[i];
    }

    for (i = 0; i <= open - 1; i++)     //判断是否在OPEN表
    {
        for (j = 0; j <= 8; j++)
        {
            if (status[j] != OPEN[i].status[j])
            {
                H++;
            }
        }

        if (H == 0)                 //H=0证明在表中找到该节点
        {
            return i + 1;           //如果在OPEN表中,返回i(节点在OPEN的位置)+ 1(在OPEN找到该节点)
        }
        H = 0;                      //扫描完一个节点后重置H
    }

    for (i = 0; i <= close - 1; i++)     //判断是否在CLOSE表
    {
        for (j = 0; j <= 8; j++)
        {
            if (status[j] != CLOSE[i].status[j])
            {
                H++;
            }
        }

        if (H == 0)                 //H=0证明在表中找到该节点
        {
            return (-i) - 1;        //如果在CLOSE表中,返回-i(i为节点在CLOSE的位置)- 1(在CLOSE找到该节点)
        }
        H = 0;                      //扫描完一个节点后重置H
    }

    return 0;
}

/*
初始化节点
返回 初始化后的节点Node
*/
ENode *ENodeInit(int *status, int zero, int g, ENode *parent, int step)
{
    int i;
    Node = new ENode;
    for (i = 0; i <= 8; i++)
    {
        Node->status[i] = status[i];
    }
    Node->Zero = zero;
    Node->G = g;
    Node->H = CountH(Node->status);
    Node->F = Node->G + Node->H;
    Node->Parent = parent;
    Node->step = step;
    return Node;
}

/*
左移后的变化
返回 左移后的状态
*/
int *Left(int *s, int z)
{
    int temp, i;
    static int status[9];
    for (i = 0; i <= 8; i++)
    {
        status[i] = s[i];
    }
    temp = status[z - 1];
    status[z - 1] = 0;
    status[z] = temp;
    return status;
}

/*
右移后的变化
返回 右移后的状态
*/
int *Right(int *s, int z)
{
    int temp, i;
    static int status[9];
    for (i = 0; i <= 8; i++)
    {
        status[i] = s[i];
    }
    temp = status[z + 1];
    status[z + 1] = 0;
    status[z] = temp;
    return status;
}

/*
上移后的变化
返回 上移后的状态
*/
int *Up(int *s, int z)
{
    int temp, i;
    static int status[9];
    for (i = 0; i <= 8; i++)
    {
        status[i] = s[i];
    }
    temp = status[z - 3];
    status[z - 3] = 0;
    status[z] = temp;
    return status;
}

/*
下移后的变化
返回 下移后的状态
*/
int *Down(int *s, int z)
{
    int temp, i;
    static int status[9];
    for (i = 0; i <= 8; i++)
    {
        status[i] = s[i];
    }
    temp = status[z + 3];
    status[z + 3] = 0;
    status[z] = temp;
    return status;
}

/*
判断子节点是否在OPEN或CLOSE中,并进行对应的操作
返回值 NULL
*/
void ExistAndOperate(ENode *N)
{
    int i;
    int inList;                 //定义表示新生成节点是否在OPEN表或CLOSE表中, 值为0 均不在,值>0 只在OPEN表,值<0 只在CLOSE表

    Node = new ENode;
    Node = N;

    if (Node->G == 1)           //如果是第一步的节点,直接加入OPEN中,返回
    {
        OPEN[open] = *Node;
        open++;
        return;
    }

    inList = Exist(Node);       //判断新节点是否在OPEN或CLOSE中

    if (inList == 0)            //如果均不在两个表中,将节点加入OPEN表中
    {
        OPEN[open] = *Node;      //将拓展出的新结点加入到OPEN表中
        open++;
    }

    else if (inList > 0)             //如果在OPEN中,说明从初始节点到该节点找到了两条路径,保留耗散值短的那条路径
    {
        if (OPEN[inList - 1].F > Node->F)    //如果表内节点F值大于新节点F值,用新节点代替表内节点
        {
            OPEN[inList - 1] = *Node;
        }
    }

    else if (inList < 0)             //如果在CLOSE中,说明初始节点到该节点有两条路径,如果新找到的路径耗散值大,什么都不做,如果较小,将其从CLOSE中取出放入OPEN中    
    {
        inList = -inList;
        if (CLOSE[inList - 1].F > Node->F)       //如果较小
        {
            OPEN[open] = *Node;       //将其取出放入OPEN
            open++;
        }
        for (i = inList - 1; i <= close - 1; i++)     //将其在CLOSE中释放
        {
            CLOSE[i] = CLOSE[i + 1];
        }
        close--;
    }
}

/*
寻找最佳路径函数
返回 最后的节点Node
*/
ENode *Search()
{
    int *status;
    int i, j;

    ENode *Temp;

    while (1)                   //一直循环知道找到解结束
    {
        Temp = new ENode;
       
        for (i = open - 1; i > 0; i--)    //用冒泡排序给OPEN表里面的节点按耗散值进行排序
        {
            for (j = 0; j < i; j++)
            {
                if (OPEN[j].F > OPEN[j + 1].F)
                {
                    *Temp = OPEN[j + 1];
                    OPEN[j + 1] = OPEN[j];
                    OPEN[j] = *Temp;
                }
            }
        }

        Node = new ENode;
        *Node = OPEN[0];                 //从OPEN表中取出第一个元素(F值最小)进行扩展

        if (!CountH(Node->status))      //判断该节点是否是目标节点,若是,则不在位的将牌数为0,算法结束
        {
            break;
        }

        Temp = Node;
        CLOSE[close] = *Node;            //将扩展过的节点放入CLOSE    
        close++;
        for (i = 0; i <= open - 1; i++) //将扩展的节点从OPEN中释放
        {
            OPEN[i] = OPEN[i + 1];
        }
        open--;

        if ((Temp->Zero) % 3 >= 1)        //如果能左移,则进行左移创造新结点    
        {
            Node = new ENode;                                           //创造新结点
            status = Left(Temp->status, Temp->Zero);                   //得到新的状态
            Node = ENodeInit(status, Temp->Zero - 1, (Temp->G) + 1, Temp, 1);    //初始化新结点
            ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
        }

        if ((Temp->Zero) % 3 <= 1)        //如果能右移,则进行右移创造新结点    
        {
            Node = new ENode;                                           //创造新结点
            status = Right(Temp->status, Temp->Zero);                   //得到新的状态
            Node = ENodeInit(status, Temp->Zero + 1, (Temp->G) + 1, Temp, 2);    //初始化新结点
            ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
        }

        if (Temp->Zero >= 3)            //如果能上移,则进行上移创造新结点    
        {
            Node = new ENode;                                           //创造新结点
            status = Up(Temp->status, Temp->Zero);                   //得到新的状态
            Node = ENodeInit(status, Temp->Zero - 3, (Temp->G) + 1, Temp, 3);    //初始化新结点
            ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
        }

        if (Temp->Zero <= 5)            //如果能下移,则进行下移创造新结点    
        {
            Node = new ENode;                                           //创造新结点
            status = Down(Temp->status, Temp->Zero);                   //得到新的状态
            Node = ENodeInit(status, Temp->Zero + 3, (Temp->G) + 1, Temp, 4);    //初始化新结点
            ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
        }

        if (open == 0)                  //如果open=0, 证明算法失败, 没有解
            return NULL;
    }
    return Node;
}

/*
展示具体步骤
返回 NULL
*/
void ShowStep(ENode *Node)
{
    int STEP[MAXSTEPSIZE];
    int STATUS[MAXSTEPSIZE][9];
    int step = 0;
    int i, j;
    int totalStep = Node->G;
    while (Node)
    {
        STEP[step] = Node->step;
        for (i = 0; i <= 8; i++)
        {
            STATUS[step][i] = Node->status[i];
        }
        step++;
        Node = Node->Parent;
    }
    cout << "----------------------" << endl;
    cout << totalStep << endl;
    cout << "----------------------" << endl;
    for (i = step - 1; i >= 0; i--)
    {
        if (STEP[i] == 1)
            cout << "left";
        else if (STEP[i] == 2)
            cout << "right";
        else if (STEP[i] == 3)
            cout << "up";
        else if (STEP[i] == 4)
            cout << "down";
        else if (STEP[i] == 0)
            cout << "START";
        cout << " ";
    }
    cout << endl << "----------------------" << endl;
    for (i = step - 1; i >= 0; i--)
    {
        for (j = 0; j <= 8; j++)
        {
            cout << STATUS[i][j];
            if (j == 2 || j == 5 || j == 8)
                cout << endl;
            else
                cout << " ";
        }
        cout << "----------------------" << endl;
    }
}

/*
主函数
返回 0
*/
int main()
{
    int fstatus[9];
    int i;
    ENode *FNode;
    ENode *EndNode;

    for (i = 0; i <= 8; i++)                    //输入初始状态
    {
        cin >> fstatus[i];
    }

    for (i = 0; i <= 8; i++)                    //判断0节点位置
    {
        if (fstatus[i] == 0)
            break;
    }

    FNode = ENodeInit(fstatus, i, 0, NULL, 0);  //获得初始节点

    OPEN[open] = *FNode;                         //将初始节点放入OPEN中
    open++;
    EndNode = Search();                         //寻找最佳路径

    if (!EndNode)
        cout << "无解" << endl;
    else
        ShowStep(EndNode);                      //展示步骤

    return 0;
}

结果

在这里插入图片描述
输入结果后,第一行显示最佳步数,第二行显示每一步的操作,接下来显示每一步的状态,成功解决问题