【问题标题】:How can I implement IDA* algorithm in Python for 15-Puzzle problem?如何在 Python 中实现 IDA* 算法以解决 15-Puzzle 问题?
【发布时间】:2021-05-21 16:48:14
【问题描述】:

我正在尝试使用 IDA* 算法和曼哈顿启发式算法来解决 15-Puzzle 问题。 我已经从这个 Wikipedia 页面 (link) 的伪代码中实现了算法。

到目前为止,这是我的代码:

def IDA(initial_state, goal_state):
    initial_node = Node(initial_state)
    goal_node = Node(goal_state)
    
    threshold = manhattan_heuristic(initial_state, goal_state)
    path = [initial_node]

    while 1:
        tmp = search(path, goal_state, 0, threshold)
        if tmp == True:
            return path, threshold
        elif tmp == float('inf'):
            return False
        else:
            threshold = tmp


def search(path, goal_state, g, threshold):
    node = path[-1]
    f = g + manhattan_heuristic(node.state, goal_state)

    if f > threshold:
        return f

    if np.array_equal(node.state, goal_state):
        return True

    minimum = float('inf')  
    for n in node.nextnodes():
        if n not in path:
            path.append(n)
            tmp = search(path, goal_state, g + 1, threshold)
            if tmp == True:
                return True
            if tmp < minimum:
                minimum = tmp
            path.pop()

    return minimum


def manhattan_heuristic(state1, state2):
    size = range(1, len(state1) ** 2)
    distances = [count_distance(num, state1, state2) for num in size]

    return sum(distances)


def count_distance(number, state1, state2):
    position1 = np.where(state1 == number)
    position2 = np.where(state2 == number)

    return manhattan_distance(position1, position2)


def manhattan_distance(a, b):
    return abs(b[0] - a[0]) + abs(b[1] - a[1])


class Node():
    def __init__(self, state):
        self.state = state

    def nextnodes(self):
        zero = np.where(self.state == 0)
        
        y,x = zero
        y = int(y)
        x = int(x)
        
        up = (y - 1, x) 
        down = (y + 1, x)
        right = (y, x + 1)
        left = (y, x - 1)

        arr = []
        for direction in (up, down, right, left):
            if len(self.state) - 1 >= direction[0] >= 0 and len(self.state) - 1 >= direction[1] >= 0:
                tmp = np.copy(self.state)
                tmp[direction[0], direction[1]], tmp[zero] = tmp[zero], tmp[direction[0], direction[1]]
                arr.append(Node(tmp))

        return arr

我正在使用 3x3 拼图测试此代码,这是无限循环!由于递归,我在测试我的代码时遇到了一些麻烦......

我认为错误可能在这里:tmp = search(path, goal_state, g + 1, threshold)(在search 函数中)。我只在 g 成本值上加了一个。但这应该是正确的,因为我只能将瓷砖移到 1 个位置。

下面是调用IDA()函数的方法:

initial_state = np.array([8 7 3],[4 1 2],[0 5 6])
goal_state = np.array([1 2 3],[8 0 4],[7 6 5])
IDA(initial_state, goal_state)

有人可以帮我解决这个问题吗?

【问题讨论】:

  • 您的示例输入是针对8-puzzle 问题的,对吧?
  • 我得到阈值24的结果。它没有进入任何无限循环,只是需要时间来收敛!您可以从here 中查看不同算法在求解8-puzzle 时的性能。

标签: python graph-algorithm path-finding heuristics sliding-tile-puzzle


【解决方案1】:

在您的 IDA* 实现中存在几个问题。首先,变量path的用途是什么?我在您的代码中发现了path 的两个用途:

  1. 用作标志/地图以保留已访问过的棋盘状态。
  2. 用作堆栈来管理递归状态。

但是,不可能同时使用一个数据结构来完成这两个任务。因此,您的代码需要的第一个修改:

  • Fix-1:将当前node 作为参数传递给search 方法。
  • Fix-2:flag 应该是可以高效执行not in 查询的数据结构。

现在,fix-1 很简单,我们只需将当前访问节点作为参数传递给 search 方法即可。对于fix-2,我们需要将flag的类型从list改为set为:

  • listx in s 的平均案例复杂度为:O(n)
  • set
    • x in s 的平均案例复杂度为:O(1)
    • x in s 的最坏情况复杂度为:O(n)

您可以查看有关performance for testing memberships: list vs sets的更多详细信息以获取更多详细信息。

现在,要将Node 信息保存到set 中,您需要在Node 类中实现__eq____hash__。下面附上修改后的代码。

import timeit
import numpy as np

def IDA(initial_state, goal_state):
    initial_node = Node(initial_state)
    goal_node = Node(goal_state)
    
    threshold = manhattan_heuristic(initial_state, goal_state)
    
    #print("heuristic threshold: {}".format(threshold))
    
    loop_counter = 0

    while 1:
        path = set([initial_node])
        
        tmp = search(initial_node, goal_state, 0, threshold, path)
        
        #print("tmp: {}".format(tmp))
        if tmp == True:
            return True, threshold
        elif tmp == float('inf'):
            return False, float('inf')
        else:
            threshold = tmp


def search(node, goal_state, g, threshold, path):
    #print("node-state: {}".format(node.state))
    f = g + manhattan_heuristic(node.state, goal_state)

    if f > threshold:
        return f

    if np.array_equal(node.state, goal_state):
        return True

    minimum = float('inf')  
    for n in node.nextnodes():
        if n not in path:
            path.add(n)
            tmp = search(n, goal_state, g + 1, threshold, path)
            if tmp == True:
                return True
            if tmp < minimum:
                minimum = tmp

    return minimum


def manhattan_heuristic(state1, state2):
    size = range(1, len(state1) ** 2)
    distances = [count_distance(num, state1, state2) for num in size]

    return sum(distances)


def count_distance(number, state1, state2):
    position1 = np.where(state1 == number)
    position2 = np.where(state2 == number)

    return manhattan_distance(position1, position2)


def manhattan_distance(a, b):
    return abs(b[0] - a[0]) + abs(b[1] - a[1])


class Node():
    def __init__(self, state):
        self.state = state
    
    def __repr__(self):
        return np.array_str(self.state.flatten())

    def __hash__(self):
        return hash(self.__repr__())
        
    def __eq__(self, other):
        return self.__hash__() == other.__hash__()

    def nextnodes(self):
        zero = np.where(self.state == 0)
        
        y,x = zero
        y = int(y)
        x = int(x)
        
        up = (y - 1, x) 
        down = (y + 1, x)
        right = (y, x + 1)
        left = (y, x - 1)

        arr = []
        for direction in (up, down, right, left):
            if len(self.state) - 1 >= direction[0] >= 0 and len(self.state) - 1 >= direction[1] >= 0:
                tmp = np.copy(self.state)
                tmp[direction[0], direction[1]], tmp[zero] = tmp[zero], tmp[direction[0], direction[1]]
                arr.append(Node(tmp))

        return arr


initial_state = np.array([[8, 7, 3],[4, 1, 2],[0, 5, 6]])
goal_state = np.array([[1, 2, 3],[8, 0, 4],[7, 6, 5]])

start = timeit.default_timer()
is_found, th = IDA(initial_state, goal_state)
stop = timeit.default_timer()

print('Time: {} seconds'.format(stop - start))

if is_found is True:
    print("Solution found with heuristic-upperbound: {}".format(th))
else:
    print("Solution not found!")

节点:请仔细检查您的 Node.nextnodes()manhattan_heuristic() 方法,因为我在这些方面没有过多关注。您可以查看此GitHub repository 以了解其他算法实现(即A*IDSDLS)来解决此问题。

参考资料:

  1. Python Wiki: Time Complexity
  2. TechnoBeans: Performance for testing memberships: list vs tuples vs sets
  3. GitHub: Puzzle Solver (by using problem solving techniques)

【讨论】:

  • 非常感谢您的回答 biqrboy,抱歉耽搁了!我尝试使用 set() 但它并没有显着加快我的算法。我按照你的建议检查了我的nextnodes(),你是对的:我可以在这里优化。但是,仍然没有显着的加速。由于我修改了很多代码,我在这里发布了一个新问题:codereview.stackexchange.com/questions/257267/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-20
  • 2020-07-22
  • 2012-08-15
  • 2011-03-20
  • 2019-12-22
  • 1970-01-01
  • 2010-09-10
相关资源
最近更新 更多