在Python中的8拼图问题

在Python中的8拼图问题

本文涵盖了8拼图问题的解决方案。提供了一个3×3的板上有8个磁贴(每个磁贴上有一个数字从1到8)和一个空白区域。目标是使用空白区域来安排磁贴上的数字,使它们与最终排列相匹配。四个相邻的磁贴(左、右、上和下)可以移动到可用区域中。

例如,
在Python中的8拼图问题

1. 深度优先搜索(暴力法)

在状态空间树上(一个特定问题的所有配置集合,即可以从初始状态到达的所有状态),我们可以进行 深度优先搜索

在Python中的8拼图问题

图:8拼图的状态空间树

在这个解决方案中,更进一步的移动并不总是将我们带到目标的更近处,而是更远离。无论初始状态如何,状态空间树总是从根节点向左侧最深处搜索。使用这种方法,可能永远无法发现答案节点。

2. BFS(暴力搜索)

我们可以使用广度优先的方法搜索状态空间树。它总是找到离根节点最近的目标状态。然而,无论初始状态如何,该算法都会尝试相同的一系列移动。

3. 分支限界法

通过避免搜索不包含答案节点的子树,一个“智能”的排名函数,也称为估计成本函数,可以经常加速寻找答案节点的过程。然而,它不使用回溯方法,而是使用广度优先的搜索。

基本上,分支限界法包括三种不同类型的节点。

  1. 一个“活”节点是一个已创建但其子节点尚未形成的节点。
  2. 正在调查后代的“E节点”是一个活节点,换句话说,一个E节点是当前正在展开的节点。
  3. 已不再展开或进一步检查的已创建节点被称为“死”节点。死节点已经扩展了其所有后代。

成本函数

在搜索树中,每个节点Y都有对应的成本。可以使用成本函数找到下一个E节点。具有最低成本的E节点就是下一个节点。该函数可以定义为:

C(Y) = g(Y) + h(Y) 
where
   g(Y) = the costs of reaching to the current node 
          from the root.
   h(Y) = the costs of reaching to an answer node from the Y.

对于一个8数码问题算法的最优代价函数为:

我们假设移动一个方块在任何方向上代价为 一单位 。基于此,我们为8数码算法创建以下代价函数:

c(y) = f(y) + h(y) 
where
   f(y) = the path's total length from the root y. 
   and
   h(y) = the amount of the non-blank tiles which are not in 
        their final goal position (misplaced tiles).

To change state y into a desired state, there are at least h(y) movements required.

有一个用于估计未知值h(y)的算法,可以访问。

最终算法

  • 为了维护”活动节点列表”,算法LCSearch使用Least()和Add()函数。
  • Least()标识一个具有最小c(y)的活动节点,从列表中删除它并返回。
  • Add(y)将y添加到活动节点列表中。
  • Add(y)将活动节点列表实现为一个最小堆。

上述算法从提供的起始配置到达8数码终态的路径如下图所示。请记住,只有具有最低cost函数值的节点被扩展。

在Python中的8拼图问题

代码:

# Python code to display the way from the root
# node to the final destination node for N*N-1 puzzle
# algorithm by the help of Branch and Bound technique
# The answer assumes that the instance of the
# puzzle can be solved

# Importing the 'copy' for deepcopy method
import copy

# Importing the heap methods from the python
# library for the Priority Queue
from heapq import heappush, heappop

# This particular var can be changed to transform
# the program from 8 puzzle(n=3) into 15
# puzzle(n=4) and so on ...
n = 3

# bottom, left, top, right
rows = [ 1, 0, -1, 0 ]
cols = [ 0, -1, 0, 1 ]

# creating a class for the Priority Queue
class priorityQueue:

    # Constructor for initializing a
    # Priority Queue
    def __init__(self):
        self.heap = []

    # Inserting a new key 'key'
    def push(self, key):
        heappush(self.heap, key)

    # funct to remove the element that is minimum,
    # from the Priority Queue
    def pop(self):
        return heappop(self.heap)

    # funct to check if the Queue is empty or not
    def empty(self):
        if not self.heap:
            return True
        else:
            return False

# structure of the node
class nodes:

    def __init__(self, parent, mats, empty_tile_posi,
                costs, levels):

        # This will store the parent node to the
        # current node And helps in tracing the
        # path when the solution is visible
        self.parent = parent

        # Useful for Storing the matrix
        self.mats = mats

        # useful for Storing the position where the
        # empty space tile is already existing in the matrix
        self.empty_tile_posi = empty_tile_posi

        # Store no. of misplaced tiles
        self.costs = costs

        # Store no. of moves so far
        self.levels = levels

    # This func is used in order to form the
    # priority queue based on
    # the costs var of objects
    def __lt__(self, nxt):
        return self.costs < nxt.costs

# method to calc. the no. of
# misplaced tiles, that is the no. of non-blank
# tiles not in their final posi
def calculateCosts(mats, final) -> int:

    count = 0
    for i in range(n):
        for j in range(n):
            if ((mats[i][j]) and
                (mats[i][j] != final[i][j])):
                count += 1

    return count

def newNodes(mats, empty_tile_posi, new_empty_tile_posi,
            levels, parent, final) -> nodes:

    # Copying data from the parent matrixes to the present matrixes
    new_mats = copy.deepcopy(mats)

    # Moving the tile by 1 position
    x1 = empty_tile_posi[0]
    y1 = empty_tile_posi[1]
    x2 = new_empty_tile_posi[0]
    y2 = new_empty_tile_posi[1]
    new_mats[x1][y1], new_mats[x2][y2] = new_mats[x2][y2], new_mats[x1][y1]

    # Setting the no. of misplaced tiles
    costs = calculateCosts(new_mats, final)

    new_nodes = nodes(parent, new_mats, new_empty_tile_posi,
                    costs, levels)
    return new_nodes

# func to print the N by N matrix
def printMatsrix(mats):

    for i in range(n):
        for j in range(n):
            print("%d " % (mats[i][j]), end = " ")

        print()

# func to know if (x, y) is a valid or invalid
# matrix coordinates
def isSafe(x, y):

    return x >= 0 and x < n and y >= 0 and y < n

# Printing the path from the root node to the final node
def printPath(root):

    if root == None:
        return

    printPath(root.parent)
    printMatsrix(root.mats)
    print()

# method for solving N*N - 1 puzzle algo
# by utilizing the Branch and Bound technique. empty_tile_posi is
# the blank tile position initially.
def solve(initial, empty_tile_posi, final):

    # Creating a priority queue for storing the live
    # nodes of the search tree
    pq = priorityQueue()

    # Creating the root node
    costs = calculateCosts(initial, final)
    root = nodes(None, initial,
                empty_tile_posi, costs, 0)

    # Adding root to the list of live nodes
    pq.push(root)

    # Discovering a live node with min. costs,
    # and adding its children to the list of live
    # nodes and finally deleting it from
    # the list.
    while not pq.empty():

        # Finding a live node with min. estimatsed
        # costs and deleting it form the list of the
        # live nodes
        minimum = pq.pop()

        # If the min. is ans node
        if minimum.costs == 0:

            # Printing the path from the root to
            # destination;
            printPath(minimum)
            return

        # Generating all feasible children
        for i in range(n):
            new_tile_posi = [
                minimum.empty_tile_posi[0] + rows[i],
                minimum.empty_tile_posi[1] + cols[i], ]

            if isSafe(new_tile_posi[0], new_tile_posi[1]):

                # Creating a child node
                child = newNodes(minimum.mats,
                                minimum.empty_tile_posi,
                                new_tile_posi,
                                minimum.levels + 1,
                                minimum, final,)

                # Adding the child to the list of live nodes
                pq.push(child)

# Main Code

# Initial configuration
# Value 0 is taken here as an empty space
initial = [ [ 1, 2, 3 ],
            [ 5, 6, 0 ],
            [ 7, 8, 4 ] ]

# Final configuration that can be solved
# Value 0 is taken as an empty space
final = [ [ 1, 2, 3 ],
        [ 5, 8, 6 ],
        [ 0, 7, 4 ] ]

# Blank tile coordinates in the 
# initial configuration
empty_tile_posi = [ 1, 2 ]

# Method call for solving the puzzle
solve(initial, empty_tile_posi, final)

输出:

1 2 3 
5 6 0 
7 8 4 

1 2 3 
5 0 6 
7 8 4 

1 2 3 
5 8 6 
7 0 4 

1 2 3 
5 8 6 
0 7 4

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程