无非就是将无序数列变成有序数列。
之间写过关于这个问题的解决办法,但是当时对各种算法理解的不是很透彻,比如广度优先算法和深度优先算法,同样都是维护一个open表,怎么用列表实现队列和栈的操作,那时候理解的不是很深,终于现在有些理解了。
Astar算法同样是维护一个open表,不过这个open表示一个优先级队列,每次取出的是代价最低的元素,然后再拓展它的邻居,然后再更新这个open表。
以上都是全局搜索策略,不管时间空间复杂度多高,它都能保证查到结果。但是以下都是局部搜索策略,并不一定保证能找到全局最优解。比如爬山法,从起点开始,寻找周围的邻居,把代价低的邻居放入open表,不断更新这个open表,直到找不到代价更低。
最抖爬山法是在爬山法是的改进,从起点开始,寻找周围的邻居,把代价最低的一个邻居放入open表,不断更新这个open表,直到找不到代价更低。
模拟退火法是根据爬山法不能找到全局最优解做的改进,爬山法每次都是往上爬,也就是每次都找代价低的点,而模拟退火法在一定概率上可以吸收代价稍高的点,但随着温度逐渐降低,这个吸收代价稍高的点的概率就越来越低。
话不多说,看代码吧,代码写的比较详细。
#Author: Slash
# DFS, BFS, Hill Climbing, A-Star with heuristics manhattan distance and hamming distance.
import heapq
class Node(object):
"""
Represent state of board in 8 puzzle problem.
"""
n = 0
def __init__(self, board, prev_state = None):
#assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常
assert len(board) == 9
self.board = board[:]
self.prev = prev_state #父节点指针
self.step = 0 #深度限制
Node.n += 1 拓展的节点数
if self.prev:
self.step = self.step + 1
def __eq__(self, other):
"""Check wether two state is equal."""
return self.board == other.board
#生成hash值。比如2 >> 1 = 1 。2 << 1 = 4
def __hash__(self):
"""Return hash code of object.
Used for comparing elements in set
"""
h = [0, 0, 0]
h[0] = self.board[0] << 6 | self.board[1] << 3 | self.board[2]
h[1] = self.board[3] << 6 | self.board[4] << 3 | self.board[5]
h[2] = self.board[6] << 6 | self.board[7] << 3 | self.board[8]
h_val = 0
for h_i in h:
h_val = h_val * 31 + h_i
return h_val
'''
#输出方式
0 1 2
3 4 5
6 7 8
'''
def __str__(self):
string_list = [str(i) for i in self.board]
sub_list = (string_list[:3], string_list[3:6], string_list[6:])
return "\n".join([" ".join(l) for l in sub_list])
def manhattan_distance(self):
"""Return Manhattan distance of state."""
#TODO: return Manhattan distance
distance = 0
goal = [1,2,3,4,5,6,7,8,0]
for i in range(1,9):
xs, ys = self.__i2pos(self.board.index(i))
xg, yg = self.__i2pos(goal.index(i))
distance += abs(xs-xg) + abs(ys-yg)
return distance
def manhattan_score(self):
"""Return Manhattan score of state."""
#TODO: return Manhattan score of state
return 0
def hamming_distance(self):
"""Return Hamming distance of state."""
#TODO: return Hamming distance
distance = 0
goal = [1,2,3,4,5,6,7,8,0]
for i in range(9):
if goal[i] != self.board[i]: distance += 1
return distance
def hamming_score(self):
"""Return Hamming distance score of state."""
#TODO return Hamming score of state
return 0
# 原理是通过控制“0”的上下左右元素互换,使得全局元素发生变化
def next(self):
"""Return next states from this state."""
next_moves = []
i = self.board.index(0) #输出0在列表中的下标
next_moves = (self.move_up(i), self.move_down(i), self.move_left(i), self.move_right(i))
return [s for s in next_moves if s]
#这里要注意:x决定了元素在第几行,y决定了元素在第几列。所以x控制上下移动,y控制左右移动
def move_right(self, i):
x, y = self.__i2pos(i)
if y < 2:
right_state = Node(self.board, self) #当前状态
right = self.__pos2i(x, y+1) #“0”元素向右移动后的下标
right_state.__swap(i, right) #得到“0”元素向右移动后的状态
return right_state
def move_left(self, i):
x, y = self.__i2pos(i)
if y > 0:
left_state = Node(self.board, self)
left = self.__pos2i(x, y - 1)
left_state.__swap(i, left)
return left_state
def move_up(self, i):
x, y = self.__i2pos(i)
if x > 0:
up_state = Node(self.board, self)
up = self.__pos2i(x - 1, y)
up_state.__swap(i, up)
return up_state
def move_down(self, i):
x, y = self.__i2pos(i)
if x < 2:
down_state = Node(self.board, self)
down = self.__pos2i(x + 1, y)
down_state.__swap(i, down)
return down_state
#将board中下标为i和下标为j的元素互换
def __swap(self, i, j):
self.board[j], self.board[i] = self.board[i], self.board[j]
#得到元素行号和列号
def __i2pos(self, index):
return (int(index / 3), index % 3)
#得到元素在列表中的下标
def __pos2i(self, x, y):
return x * 3 + y
class PriorityQueue:
def __init__(self):
self.heap = []
self.count = 0
def push(self, item, priority):
# FIXME: restored old behaviour to check against old results better
# FIXED: restored to stable behaviour
entry = (priority, self.count, item)
# entry = (priority, item)
heapq.heappush(self.heap, entry)
self.count += 1
def pop(self):
(_, _, item) = heapq.heappop(self.heap)
# (_, item) = heapq.heappop(self.heap)
return item
def isEmpty(self):
return len(self.heap) == 0
class Searcher(object):
"""Searcher that manuplate searching process."""
def __init__(self, start, goal):
self.start = start
self.goal = goal
def print_path(self, state):
path = []
while state:
path.append(state)
state = state.prev
path.reverse() #队列翻转
print("\n-->\n".join([str(state) for state in path]))
#通过维护一个栈实现
#这样想,首先用append()从尾部添加一个,又用pop()从尾部拿出一个,这样不就是后进先出吗
#或者用deque()实现。首先用popleft()从头部拿出一个,又用appendleft()从头部添加一个,这样不就是先进先出吗
def dfs(self, depth = 100):
stack = [self.start]
visited = set()
found = False
while stack:
state = stack.pop()
if state == self.goal:
found = state
break
if state in visited or state.step > depth:
continue
visited.add(state)
for s in state.next():
s.prev = state #调整父节点指针
stack.append(s)
if found:
self.print_path(found)
print ("Find solution")
else:
print("No solution found")
#通过维护一个队列实现
#这样想,首先用insert()从头部插入一个,再用pop()从尾部拿出一个,这样不就是先进后出吗
#或者用deque()实现。首先用popleft()从头部拿出一个,又用append()从尾部添加一个,这样不就是先进后出吗
def bfs(self, depth = 50):
"""Run Breadth-first search."""
#TODO: Implement breadth first search
queue = [self.start]
visited = set()
found = False
while queue:
state = queue.pop()
if state == self.goal:
found = state
break
if state in visited or state.step > depth:
continue
visited.add(state)
for s in state.next():
s.prev = state #调整父节点指针
queue.insert(0, s)
if found:
self.print_path(found)
print ("Find solution")
else:
print("No solution found")
#通过维护一个优先级队列实现,每次拿出F最小的元素
def astar(self, depth = 75):
"""Run A* search."""
#TODO: Implement a star search.
priotity_queue = PriorityQueue()
#初始的启发函数。
h_val = self.start.manhattan_distance() + self.start.hamming_distance()
# g_val always is start.step
#与局部搜索的区别在于A*是启发搜索,所以把初始F放在这。
f_val = h_val + self.start.step
priotity_queue.push(self.start, f_val)
visited = set()
found = False
while not priotity_queue.isEmpty():
state = priotity_queue.pop()
if state == self.goal:
found = state
break
if state in visited or state.step > depth:
continue
visited.add(state)
for s in state.next():
#寻路中的启发函数
s.prev = state #调整父节点指针
h_val_s = s.manhattan_distance() + s.hamming_distance()
f_val_s = h_val_s + s.step
priotity_queue.push(s, f_val_s)
if found:
self.print_path(found)
print ("Find solution")
else:
print("No solution found")
# 依次寻找该点X的邻近点中首次出现的比点X价值高的点,并将该点作为爬山的点. 依次循环,直至该点的邻近点中不再有比其大的点. 我们成为该点就是山的顶点,又称为最优点.
def hill_climbing(self):
"""Run hill climbing search."""
#TODO Implement hill climbing.
stack = [self.start]
while stack:
state = stack.pop()
if state == self.goal:
self.print_path(state)
print ("Find solution")
break
#计算当前状态的启发f函数
h_val = state.manhattan_distance() + state.hamming_distance()
next_state = False
for s in state.next():
h_val_next = s.manhattan_distance() + s.hamming_distance()
if h_val_next < h_val:
s.prev = state
next_state = s
h_val = h_val_next
#与最抖爬山法的区别在于它将每一个邻居都放入stack。而最抖爬山法只将代价最小的放入stack。
stack.append(next_state)
break
if not next_state:
self.print_path(state)
print ("Cannot find solution")
# 最陡爬山算法是在首选爬山算法上的一种改良,它规定每次选取邻近点价值最大的那个点作为爬上的点
def steepest_ascent_hill_climbing(self):
"""Run steepest ascent hill climbing search."""
#TODO Implement hill climbing.
stack = [self.start]
while stack:
state = stack.pop()
if state == self.goal:
self.print_path(state)
print ("Find solution")
break
h_val = state.manhattan_distance() + state.hamming_distance()
next_state = False
for s in state.next():
h_val_next = s.manhattan_distance() + s.hamming_distance()
if h_val_next < h_val:
s.prev = state #调整父节点指针
next_state = s
h_val = h_val_next
if next_state:
stack.append(next_state)
else:
self.print_path(state)
print ("Cannot find solution")
#模拟退火方法与爬山法类似,爬山法是一直向上爬,只吸收代价最低的节点,擅长找到局部最优解。而模拟退火法以一定概率吸收代价不是最低的点,
#这样避免了只能找到局部最优解的情况,有可能找到全局最优解。
# 算法接受较差解的概率 P = exp[-(highcost-lowcost)/temperature]
def simulated_annealing(self):
"""Run Simulated Annealing Search"""
#TODO Implement Simulated Annealing.
#先初始化
stack = [self.start]
T=10000.0
cool=0.98
while stack:
state = stack.pop()
if state == self.goal:
self.print_path(state)
print ("Find solution")
break
#计算当前状态的启发f函数
h_val = state.manhattan_distance() + state.hamming_distance()
next_state = False
for s in state.next():
h_val_next = s.manhattan_distance() + s.hamming_distance()
#%温度越低,越不太可能接受新解;新老距离差值越大,越不太可能接受新解
if h_val_next < h_val or random.random() < math.exp(-(h_val_next - h_val) / T):
s.prev = state
next_state = s
h_val = h_val_next
stack.append(next_state)
break
T = T*cool
if not next_state:
self.print_path(state)
print ("Cannot find solution")
if __name__ == "__main__":
script, strategy = argv
#Unit test
print("Search for solution\n")
start = Node([2,0,1,4,5,3,8,7,6])
goal = Node([1,2,3,4,5,6,7,8,0])
#print start.hamming_distance()
#print start.manhattan_distance()
search = Searcher(start, goal)
start_time = time()
if strategy == "dfs":
search.dfs()
elif strategy == "bfs":
search.bfs()
elif strategy == "hc":
search.hill_climbing()
elif strategy == "sahc":
search.steepest_ascent_hill_climbing()
elif strategy == "astar":
search.astar()
elif strategy == "sa":
search.simulated_annealing()
else:
print ("Wrong strategy")
end_time = time()
elapsed = end_time - start_time
print ("Search time: %s" % elapsed)
print ("Number of initialized node: %d" % Node.n)