数字华容道-15拼图问题

2,864 阅读5分钟

题意

最近要做一道题目,题目的意思是:

用尽量少的步数,尽量短的时间,将棋盘上的数字方块,按照从左到右、从上到下的顺序重新排列整齐

1.首先要判断是否可达

2.其次使用的是3种算法来解决

a.深度优先搜索

b.广度优先搜索

c.迭代加深的深度优先搜索

img

【上图为三阶】

思路

把问题看成0在往4个方向走,不断的与相邻的数进行交换形成新的状态图,但要注意重复出现的状态,也要注意移动的时候是否触及边界。0的走法不唯一,所以有bfs,dfs和id-dfs。

如何判断是否有解?

  • 若格子列数为奇数,则逆序数必须为偶数
  • 若格子列数为偶数,且逆序数为偶数,则当前空格所在行数与初始空格所在行数的差为偶数
  • 若格子列数为偶数,且逆序数为奇数,则当前空格所在行数与初始空格所在行数的差为奇数

如何求逆序数个数?

a = np.array(self.arr).flatten()
b = list(a)
b.remove(0)
ans = 0
for i in range(len(b)):
    for j in range(i + 1, len(b)):
        if b[i] > b[j]:
            ans += 1
return ans 

如何找到0的位置?

index = np.argwhere(np.array(self.arr) == 0)
return index[0][0], index[0][1]

如何对0进行移动?

def move_0(self, direction):
    """
        移动0的位置
        :param direction: U D L R 上下左右
        :return: 是否可移动,移动后的节点
        """
    # 复制当前矩阵数据
    new_arr = deepcopy(self.arr)
    # 找到0的位置
    x, y = self.find_0()
    # 判断移动方向进行移动+判断边界
    if direction == 'U':
        if x == 0:
            return False, None
        else:
            new_arr[x][y] = new_arr[x - 1][y]
            new_arr[x - 1][y] = 0
            elif direction == 'D':
                if x == len(new_arr) - 1:  # 如果x在最后一行
                    return False, None
                else:
                    new_arr[x][y] = new_arr[x + 1][y]
                    new_arr[x + 1][y] = 0
                    elif direction == 'L':
                        if y == 0:
                            return False, None
                        else:
                            new_arr[x][y] = new_arr[x][y - 1]
                            new_arr[x][y - 1] = 0
                            elif direction == 'R':
                                if y == len(new_arr) - 1:
                                    return False, None
                                else:
                                    new_arr[x][y] = new_arr[x][y + 1]
                                    new_arr[x][y + 1] = 0
                                    return True, Node(arr=new_arr, parent=self, depth=self.depth + 1)

BFS

广度优先搜索算法,需要用借助一个队列和一个列表来实现。

def bfs(node, last_node):
        """
        广度优先搜索 此处主要用队列处理
        :param node: 初始节点
        :param last_node: 目标节点
        :return:
        """
        operate_queue = []
        finished_queue = []
        operate_queue.append(node)
        while True:
            print('operated number:{}'.format(len(operate_queue)))
            if not operate_queue:
                return False, None
            queue_first_node = operate_queue.pop(0)
            finished_queue.append(queue_first_node)
            for direction in directions:
                has_result, next_node = queue_first_node.move_0(direction)
                if has_result and next_node not in finished_queue:
                    operate_queue.append(next_node)
                    if next_node == last_node:
                        return True, next_node

DFS

深度优先搜索算法,需要借助一个栈和一个列表来实现。

 def dfs(node, last_node):
        """
        深度优先搜索 此处主要用栈处理
        :param node: 初始节点
        :param last_node: 目标节点
        :return:
        """
        operate_stack = []
        finished_queue = []
        operate_stack.append(node)
        while True:
            print('operated number:{}'.format(len(operate_stack)))
            if not operate_stack:
                return False, None
            stack_top_node = operate_stack.pop()
            finished_queue.append(stack_top_node)
            for direction in directions:
                has_result, next_node = stack_top_node.move_0(direction)
                if has_result and next_node not in finished_queue:
                    operate_stack.append(next_node)
                    if next_node == last_node:
                        return True, next_node

ID-DFS

迭代加深的深度优先搜索算法,需要借助一个栈和两个列表来实现

def id_dfs(node, last_node, k=10):
        """
        迭代加深的深度优先算法
        :param node: 初始节点
        :param last_node: 目标节点
        :param k: 预定义的k层深度
        :return:
        """
        # 待处理的节点放进栈中
        operate_stack = []
        # 已遍历的节点放进完成队列
        finished_queue = []
        # 因大于k层放入临时列表
        tmp = []
        operate_stack.append(node)
        while True:
            print('operated number:{}'.format(len(operate_stack)))
            if not operate_stack:
                if tmp:
                    operate_stack = tmp
                    tmp = []
                    k = k + 1
                else:
                    return False, None
            stack_top_node = operate_stack.pop()
            print("First judge depth :{},k:{}".format(stack_top_node.depth, k))
            if stack_top_node.depth < k:
                finished_queue.append(stack_top_node)
                for direction in directions:
                    has_result, next_node = stack_top_node.move_0(direction)
                    if has_result and next_node not in finished_queue:
                        print("Second judge depth :{},k:{}".format(next_node.depth, k))
                        if next_node.depth <= k:
                            if next_node == last_node:
                                return True, next_node
                            operate_stack.append(next_node)
                        else:
                            tmp.append(next_node)
            else:
                tmp.append(stack_top_node)

完整代码

import numpy as np
from copy import deepcopy
from enum import Enum

# 代表方向上下左右
directions = ['U', 'D', 'L', 'R']


# 定义枚举类,三种算法
class Algorithm(Enum):
    BFS = 1
    DFS = 2
    ID_DFS = 3


# 定义节点
class Node:

    def __init__(self, arr, parent, depth=1):
        """
        初始化
        :param arr: 二维数组
        :param parent: 父节点
        :param depth: 深度
        """
        self.arr = arr
        self.parent = parent
        self.depth = depth

    def find_0(self):
        """
        找到0的位置
        :return: 坐标索引
        """
        index = np.argwhere(np.array(self.arr) == 0)
        return index[0][0], index[0][1]

    def move_0(self, direction):
        """
        移动0的位置
        :param direction: U D L R 上下左右
        :return: 是否可移动,移动后的节点
        """
        # 复制当前矩阵数据
        new_arr = deepcopy(self.arr)
        # 找到0的位置
        x, y = self.find_0()
        # 判断移动方向进行移动+判断边界
        if direction == 'U':
            if x == 0:
                return False, None
            else:
                new_arr[x][y] = new_arr[x - 1][y]
                new_arr[x - 1][y] = 0
        elif direction == 'D':
            if x == len(new_arr) - 1:  # 如果x在最后一行
                return False, None
            else:
                new_arr[x][y] = new_arr[x + 1][y]
                new_arr[x + 1][y] = 0
        elif direction == 'L':
            if y == 0:
                return False, None
            else:
                new_arr[x][y] = new_arr[x][y - 1]
                new_arr[x][y - 1] = 0
        elif direction == 'R':
            if y == len(new_arr) - 1:
                return False, None
            else:
                new_arr[x][y] = new_arr[x][y + 1]
                new_arr[x][y + 1] = 0
        return True, Node(arr=new_arr, parent=self, depth=self.depth + 1)

    def get_way(self):
        """
        回溯获取路径
        :return: 节点列表
        """
        way = []
        cur = self
        while cur is not None:
            way.insert(0, cur)
            cur = cur.parent
        return way

    def print_array(self):
        """
        输出矩阵
        :return:
        """
        print(np.array(self.arr))

    def __eq__(self, other):
        """
        重写eq 判断两个节点的矩阵是否相同即可
        :param other:
        :return:
        """
        return self.arr == other.arr if other else False

    def has_answer(self, last_node):
        """
        求逆序个数(除了0) 判断是否有解
        :return: 有解则为True
        """
        a = np.array(self.arr).flatten()
        b = list(a)
        b.remove(0)
        ans = 0
        for i in range(len(b)):
            for j in range(i + 1, len(b)):
                if b[i] > b[j]:
                    ans += 1

        # 若格子列数为奇数,则逆序数必须为偶数;
        # 若格子列数为偶数,且逆序数为偶数,则当前空格所在行数与初始空格所在行数的差为偶数;
        # 若格子列数为偶数,且逆序数为奇数,则当前空格所在行数与初始空格所在行数的差为奇数。

        if len(a) % 2 == 0:
            x0, y0 = self.find_0()
            x1, y1 = last_node.find_0()
            return (ans % 2 == 0 and abs(y1 - y0) % 2 == 0) or (ans % 2 != 0 and abs(y1 - y0) % 2 != 0)
        else:
            return ans % 2 == 0


# 定义解决器
class Solver:
    def __init__(self, first_node, last_node):
        """
        初始化 最初矩阵与目标矩阵
        :param first_node: 初始矩阵
        :param last_node: 目标矩阵
        """
        self.first_node = first_node
        self.last_node = last_node

    def solve(self, alg):
        """
        处理15拼图问题
        :param alg: 选取的算法
        :return:
        """
        if self.first_node.has_answer(self.last_node):
            if alg == Algorithm.BFS:
                result, node = self.bfs(self.first_node, self.last_node)
            elif alg == Algorithm.DFS:
                result, node = self.dfs(self.first_node, self.last_node)
            elif alg == Algorithm.ID_DFS:
                result, node = self.id_dfs(self.first_node, self.last_node, k=10)
            else:
                print("error~wrong algorithm")
                return
            # 输出路径
            if result:
                print("get answer as follow:")
                way = node.get_way()
                for w in way:
                    if w:
                        w.print_array()
                return
        print("no answer~")

    @staticmethod
    def id_dfs(node, last_node, k=10):
        """
        迭代加深的深度优先算法
        :param node: 初始节点
        :param last_node: 目标节点
        :param k: 预定义的k层深度
        :return:
        """
        # 待处理的节点放进栈中
        operate_stack = []
        # 已遍历的节点放进完成队列
        finished_queue = []
        # 因大于k层放入临时列表
        tmp = []
        operate_stack.append(node)
        while True:
            print('operated number:{}'.format(len(operate_stack)))
            if not operate_stack:
                if tmp:
                    operate_stack = tmp
                    tmp = []
                    k = k + 1
                else:
                    return False, None
            stack_top_node = operate_stack.pop()
            print("First judge depth :{},k:{}".format(stack_top_node.depth, k))
            if stack_top_node.depth < k:
                finished_queue.append(stack_top_node)
                for direction in directions:
                    has_result, next_node = stack_top_node.move_0(direction)
                    if has_result and next_node not in finished_queue:
                        print("Second judge depth :{},k:{}".format(next_node.depth, k))
                        if next_node.depth <= k:
                            if next_node == last_node:
                                return True, next_node
                            operate_stack.append(next_node)
                        else:
                            tmp.append(next_node)
            else:
                tmp.append(stack_top_node)

    @staticmethod
    def dfs(node, last_node):
        """
        深度优先搜索 此处主要用栈处理
        :param node: 初始节点
        :param last_node: 目标节点
        :return:
        """
        operate_stack = []
        finished_queue = []
        operate_stack.append(node)
        while True:
            print('operated number:{}'.format(len(operate_stack)))
            if not operate_stack:
                return False, None
            stack_top_node = operate_stack.pop()
            finished_queue.append(stack_top_node)
            for direction in directions:
                has_result, next_node = stack_top_node.move_0(direction)
                if has_result and next_node not in finished_queue:
                    operate_stack.append(next_node)
                    if next_node == last_node:
                        return True, next_node

    @staticmethod
    def bfs(node, last_node):
        """
        广度优先搜索 此处主要用队列处理
        :param node: 初始节点
        :param last_node: 目标节点
        :return:
        """
        operate_queue = []
        finished_queue = []
        operate_queue.append(node)
        while True:
            print('operated number:{}'.format(len(operate_queue)))
            if not operate_queue:
                return False, None
            queue_first_node = operate_queue.pop(0)
            finished_queue.append(queue_first_node)
            for direction in directions:
                has_result, next_node = queue_first_node.move_0(direction)
                if has_result and next_node not in finished_queue:
                    operate_queue.append(next_node)
                    if next_node == last_node:
                        return True, next_node


if __name__ == '__main__':
    last_arr = [[1, 2, 3, 4],
                [5, 6, 7, 8],
                [9, 10, 11, 12],
                [13, 14, 15, 0]]

    first_arr = [[3, 2, 1, 4],
                 [7, 6, 5, 8],
                 [11, 10, 9, 12],
                 [13, 15, 14, 0]]
    #
    # last_arr = [[1, 2, 3, 4],
    #             [5, 6, 7, 8],
    #             [9, 10, 11, 12],
    #             [13, 14, 15, 0]]
    #
    # first_arr =[[1, 2, 3, 4],
    #             [5, 6, 7, 8],
    #              [10, 12, 0, 14],
    #              [9, 11, 13, 15]]
    # last_arr = [[1, 2, 3],
    #             [4, 5, 6],
    #             [7, 8, 0]]
    #
    # first_arr = [[1, 3, 4],
    #              [8, 6, 5],
    #              [2, 7, 0]]

    # last_arr = [[1, 2],
    #             [3, 0]]
    #
    # first_arr = [[0, 1],
    #              [3, 2]]

    solver = Solver(Node(arr=first_arr, parent=None), Node(arr=last_arr, parent=None))
    solver.solve(Algorithm.ID_DFS)