N數碼問題的啟發式搜索算法--A*算法python實現
- 一、啟發式搜索:A算法
1)評價函數的一般形式 : f(n) = g(n) + h(n)
g(n):從S0到Sn的實際代價(搜索的橫向因子)
h(n):從N到目標節點的估計代價,稱為啟發函數(搜索的縱向因子);
特點: 效率高, 無回溯,
搜索算法
OPEN表 : 存放待擴展的節點.
CLOSED表 : 存放已被擴展過的節點.
2)評價函數 f(x) = g(x) + h(x)
當f(x) = g(x) 時,為寬度優先搜索
當f(x) = 1/g(x)時,為深度優先搜索
當f(x) = h(x) 時,為全局優先搜索
比較f(x)大小,決定節點搜索順序,即在OPEN表中的順序
3)Step1: 把初始節點S0放入OPEN表中;
Step2: 若OPEN表為空,則搜索失敗,退出.
Step3: 移出OPEN中第一個節點N放入CLOSED表中, 並標以順序號n;
Step4: 若目標節點Sg=N, 則搜索成功,結束.
Step5: 若N不可擴展, 則轉Step2;
Step6: 擴展N, 生成一組子節點, 對這組子節點作如下處理後, 放入 OPEN表, 按f值重新排序OPEN表, 轉 Step2;
刪除重復節點和修改返回指針處理.
- 二、啟發式搜索:A*算法
1)評價函數的一般形式:
f(n) = g(n) + h(n) 且 h(n) <= h*(n)
g(n),h(n):定義同A算法;
h*(n):從N到目標節點的最短路徑; 稱此時的A算法為A*算法.
2)程序關鍵點
節點的擴展:close表存放已經擴展後的狀態,open表存放未擴展的狀態。首先獲取節點能擴展的方向,擴展後將父節點放入close表中,如果轉移之後的節點,既不在close表也不再open表,表明該節點還未被擴展,則插入open表,如果在close表中表明之前已經擴展過該狀態,為了避免無限擴展應將該狀態從open表舍棄,如果在open表則比較這兩個矩陣的f值(選取最優解),留小的在open表,之後對open表中的節點根據f值進行排序,pop出f值最小的節點進行擴展,依次進行該過程,直至該節點為目標狀態。解的路徑的輸出:通過目標狀態節點向上回溯找其父節點,直至開始狀態。
- 三、python代碼實現
1 # -*- coding: utf-8 -*- 2 """ 3 Created on Sun Sep 16 14:31:40 2018 4 A*算法解決N數碼問題 5 運行程序後如下是輸入格式: 6 請輸入矩陣的行數 7 8 3 輸入對應的N 9 請輸入初始矩陣A 10 11 1 0 2 一行行輸入,每行數字空格隔開,每行最後一個數字輸入完成後直接回車開始輸入第二行 12 13 4 5 6 14 15 3 7 8 16 請輸入目標矩陣B 17 18 1 2 3 19 20 8 0 4 21 22 7 6 5 23 24 """ 25 import numpy as np 26 import copy 27 import time 28 from operator import itemgetter 29 30 goal = {} 31 32 def get_location(vec, num): #根據num元素獲取num在矩陣中的位置 33 row_num = vec.shape[0] #numpy-shape函數獲得矩陣的維數 34 line_num = vec.shape[1] 35 36 for i in range(row_num): 37 for j in range(line_num): 38 if num == vec[i][j]: 39 return i, j 40 41 def get_actions(vec): #獲取當前位置可以移動的下一個位置,返回移動列表 42 row_num = vec.shape[0] 43 line_num = vec.shape[1] 44 45 (x, y) = get_location(vec, 0) #獲取0元素的位置 46 action = [(0, 1), (0, -1), (1, 0), (-1, 0)] 47 48 if x == 0: #如果0在邊緣則依據位置情況,減少0的可移動位置 49 action.remove((-1, 0)) 50 if y == 0: 51 action.remove((0, -1)) 52 if x == row_num - 1: 53 action.remove((1, 0)) 54 if y == line_num - 1: 55 action.remove((0, 1)) 56 57 return list(action) 58 59 def result(vec, action): #移動元素,進行矩陣轉化 60 (x, y) = get_location(vec, 0) #獲取0元素的位置 61 (a, b) = action #獲取可移動位置 62 63 n = vec[x+a][y+b] #位置移動,交換元素 64 s = copy.deepcopy(vec) 65 s[x+a][y+b] = 0 66 s[x][y] = n 67 68 return s 69 70 def get_ManhattanDis(vec1, vec2): #計算兩個矩陣的曼哈頓距離,vec1為目標矩陣,vec2為當前矩陣 71 row_num = vec1.shape[0] 72 line_num = vec1.shape[1] 73 dis = 0 74 75 for i in range(row_num): 76 for j in range(line_num): 77 if vec1[i][j] != vec2[i][j] and vec2[i][j] != 0: 78 k, m = get_location(vec1, vec2[i][j]) 79 d = abs(i - k) + abs(j - m) 80 dis += d 81 82 return dis 83 84 def expand(p, actions, step): #actions為當前矩陣的可擴展狀態列表,p為當前矩陣,step為已走的步數 85 children = [] #children用來保存當前狀態的擴展節點 86 for action in actions: 87 child = {} 88 child[‘parent‘] = p 89 child[‘vec‘] = (result(p[‘vec‘], action)) 90 child[‘dis‘] = get_ManhattanDis(goal[‘vec‘], child[‘vec‘]) 91 child[‘step‘] = step + 1 #每擴展一次當前已走距離加1 92 child[‘dis‘] = child[‘dis‘] + child[‘step‘] #更新該節點的f值 f=g+h(step+child[dis]) 93 child[‘action‘] = get_actions(child[‘vec‘]) 94 children.append(child) 95 96 return children 97 98 def node_sort(nodelist): #按照節點中字典的距離字段對列表進行排序,從大到小 99 return sorted(nodelist, key = itemgetter(‘dis‘), reverse=True) 100 101 def get_input(num): 102 A = [] 103 for i in range(num): 104 temp = [] 105 p = [] 106 s = input() 107 temp = s.split(‘ ‘) 108 for t in temp: 109 t = int(t) 110 p.append(t) 111 A.append(p) 112 113 return A 114 115 def get_parent(node): 116 q = {} 117 q = node[‘parent‘] 118 return q 119 120 def test(): 121 openlist = [] #open表 122 close = [] #存儲擴展的父節點 123 124 print(‘請輸入矩陣的行數‘) 125 num = int(input()) 126 127 print("請輸入初始矩陣A") 128 A = get_input(num) 129 130 print("請輸入目標矩陣B") 131 B = get_input(num) 132 133 print("請輸入結果文件名") 134 resultfile = input() 135 136 goal[‘vec‘] = np.array(B) #建立矩陣 137 138 p = {} 139 p[‘vec‘] = np.array(A) 140 p[‘dis‘] = get_ManhattanDis(goal[‘vec‘], p[‘vec‘]) 141 p[‘step‘] = 0 142 p[‘action‘] = get_actions(p[‘vec‘]) 143 p[‘parent‘] = {} 144 145 if (p[‘vec‘] == goal[‘vec‘]).all(): 146 return 147 148 openlist.append(p) 149 150 start_CPU = time.clock() #開始擴展時CPU開始計算 151 152 while openlist: 153 154 children = [] 155 156 node = openlist.pop() #node為字典類型,pop出open表的最後一個元素 157 close.append(node) #將該元素放入close表 158 159 if (node[‘vec‘] == goal[‘vec‘]).all(): #比較當前矩陣和目標矩陣是否相同 160 end_CPU = time.clock() #CPU結束計算 161 162 h = open(resultfile,‘w‘,encoding=‘utf-8‘,) #將結果寫入文件 並在控制臺輸出 163 h.write(‘搜索樹規模:‘ + str(len(openlist)+len(close)) + ‘\n‘) 164 h.write(‘close:‘ + str(len(close)) + ‘\n‘) 165 h.write(‘openlist:‘ + str(len(openlist)) + ‘\n‘) 166 h.write(‘cpu運行時間:‘ + str(end_CPU - start_CPU) + ‘\n‘) 167 h.write(‘路徑長:‘ + str(node[‘dis‘]) + ‘\n‘) 168 169 h.write(‘解的路徑:‘ + ‘\n‘) 170 i = 0 171 way = [] 172 while close: 173 way.append(node[‘vec‘]) #從最終狀態開始依次向上回溯將其父節點存入way列表中 174 node = get_parent(node) 175 if(node[‘vec‘] == p[‘vec‘]).all(): 176 way.append(node[‘vec‘]) 177 break 178 while way: 179 i += 1 180 h.write(str(i) + ‘\n‘) 181 h.write(str(way.pop()) + ‘\n‘) 182 h.close() 183 f = open(resultfile,‘r‘,encoding=‘utf-8‘,) 184 print(f.read()) 185 186 return 187 188 children = expand(node, node[‘action‘], node[‘step‘]) #如果不是目標矩陣,對當前節點進行擴展,取矩陣的可能轉移情況 189 190 for child in children: #如果轉移之後的節點,既不在close表也不再open表則插入open表,如果在close表中則舍棄,如果在open表則比較這兩個矩陣的f值,留小的在open表 191 f = False 192 flag = False 193 j = 0 194 for i in range(len(openlist)): 195 if (child[‘vec‘] == openlist[i][‘vec‘]).all(): 196 j = i 197 flag = True 198 break 199 for i in range(len(close)): 200 if(child[‘vec‘] == close[i]).all(): 201 f = True 202 break 203 if f == False and flag == False : 204 openlist.append(child) 205 206 elif flag == True: 207 if child[‘dis‘] < openlist[j][‘dis‘]: 208 del openlist[j] 209 openlist.append(child) 210 211 212 openlist = node_sort(openlist) #對open表進行從大到小排序 213 214 test()
- 四、程序運行結果如下圖所示
圖 1
圖 2
圖 3
- 五、總結
通過這次編程了解到了搜索具有探索性,要提高搜索效率(盡快地找到目標節點),或要找最佳路徑(最佳解)就必須註意搜索策略。對於狀態圖搜索,已經提出了許多策略,它們大體可分為盲目搜索(bland search)和啟發式搜索(heuristic search)兩大類。其中盲目搜索是無向導搜索。啟發式搜索是有向導搜索,即利用啟發信息(函數)引導去尋找問題解。通過A*算法解決N數碼問題實驗過程中也遇到很多問題,比如節點擴展的方向問題等,通過這次實驗不僅鍛煉了自己python編程能力,也讓自己對N數碼求解最優路徑問題有了更清晰的認識,希望自己能在老師和同學的幫助下,能不斷進步,當然最重要的是自己得付出,只會幻想而不行動的人,永遠也體會不到收獲果實時的喜悅。加油!!
N數碼問題的啟發式搜索算法--A*算法python實現