八数码问题
八数码问题(重排九宫问题)是在一个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; } |
结果
输入结果后,第一行显示最佳步数,第二行显示每一步的操作,接下来显示每一步的状态,成功解决问题