NO.79——BFS,DFS,Astar,爬山法,最抖爬山法,模拟退火法解决八数码问题Python实现

问题描述

     无非就是将无序数列变成有序数列。

之间写过关于这个问题的解决办法,但是当时对各种算法理解的不是很透彻,比如广度优先算法深度优先算法,同样都是维护一个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

DFS

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")

BFS

    #通过维护一个队列实现
    #这样想,首先用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")

Astar

    #通过维护一个优先级队列实现,每次拿出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")

hill_climb

    # 依次寻找该点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")

steepest_ascent_hill_climbing

    # 最陡爬山算法是在首选爬山算法上的一种改良,它规定每次选取邻近点价值最大的那个点作为爬上的点
    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")

simulated annealing

    #模拟退火方法与爬山法类似,爬山法是一直向上爬,只吸收代价最低的节点,擅长找到局部最优解。而模拟退火法以一定概率吸收代价不是最低的点,
    #这样避免了只能找到局部最优解的情况,有可能找到全局最优解。
    # 算法接受较差解的概率 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)

NO.79——BFS,DFS,Astar,爬山法,最抖爬山法,模拟退火法解决八数码问题Python实现_第1张图片

你可能感兴趣的:(机器学习,python)