用A*算法解决翻箱问题

 看到问答区有个问题:

集装箱码头在堆场任务过程中(装船或提箱)存在翻箱任务,原因是由于要求的发箱顺序和码头堆放的次序不符合,如图所示,其中数字代表发箱顺序(数字越小越早发箱,可以有重复),S代表堆放的排数量(<=6),T代表堆放的高度上限(考虑堆场机任务业能力等因素,<=5),最小预翻箱问题就是,通过一系列的尽可能少的翻箱动作(如(2,2)→(4,4),(3,1)→(3,2)),把堆场倍位初始状态数字小的箱子都翻上来,避免实际任务(装船或提箱)时由于翻箱造成的任务效率损失。设计和实现最小预翻箱算法,完成如下问题:
1.基于启发式规则的A星算法用python实现(2),并输出和验证求解结果(翻箱步骤)(初始发箱顺序可随机生成),翻箱过程中不违背堆放的高度上限。

img

我试着解决了一下。A*算法本身没太多可说的,网上已经有很多的文章介绍。关键是确定合适的启发函数,这里我用了:

    def _h(self):
        length = len(self.stack.get_incorrect_containers())
        adjustment = self.value / (self.stack.total_row * self.stack.max_height)
        return length + 1 - adjustment

意思是如果某个翻箱动作能使错误摆放的箱子数最少,那么优先考虑该翻箱动作。如果所有可能的翻箱动作的错误箱子数相同,那么优先移动顺序最靠后的一个。

特别的,按照程序执行的结果,问题中的例子其实可以5步解决,不需要6步。

完整代码如下:

import operator

S = 4
T = 4
# 每一排箱子的发箱顺序
origin_stack = [[9, 1, 5, 0], [8, 0, 0, 0], [2, 11, 4, 0], [10, 7, 3, 6]]


# 代表堆场的类
class Stack(object):
    def __init__(self, values, total_row, max_height):
        # 所有箱子
        self.containers = values
        # 一共多少排
        self.total_row = total_row
        # 最大高度
        self.max_height = max_height
        # 当前各排实际高度
        self.height = [max_height] * total_row
        for i in range(total_row):
            self.height[i] = -1
            for j in range(max_height):
                if values[i][j] != 0:
                    self.height[i] = j

    # 取得某一排最上面一个箱子
    def get_top_container(self, row):
        if self.height[row] != -1:
            return self.containers[row][self.height[row]]
        else:
            return None

    # 修改某个位置箱子的发箱顺序
    def set_container(self, position, value):
        self.containers[position[0]][position[1]] = value

    # 取得指定位置的箱子
    def get_container(self, position):
        return self.containers[position[0]][position[1]]

    # 列举当前有哪些可移动步骤:只有最顶上的箱子可以被移动到其它有空位的排的顶上
    def list_available_steps(self, close_steps):
        available_steps = []
        for i in range(self.total_row):
            if self.height[i] < 0:
                continue

            for j in range(self.total_row):
                if i != j and self.height[j] < self.max_height - 1:
                    cloned_stack = self.__copy__()
                    top_container = cloned_stack.get_top_container(i)
                    new_step = Step(top_container, (i, self.height[i]), (j, self.height[j] + 1), cloned_stack)
                    if new_step not in close_steps:
                        available_steps.append(new_step)
        return available_steps

    # 取得所有摆放位置不对的箱子
    def get_incorrect_containers(self, row=-1):
        if row == -1:
            return self._get_incorrect_containers(range(self.total_row))
        else:
            return self._get_incorrect_containers(range(row, row + 1))

    # 取得所有摆放位置不对的箱子:如果发箱顺序小的箱子位于发箱顺序大的箱子之下,那么认为这个发箱顺序小的箱子向上的所有箱子位置都不对
    def _get_incorrect_containers(self, rows):
        result = []
        for i in rows:
            for j in range(1, self.max_height):
                if self.containers[i][j] > self.containers[i][j - 1]:
                    for k in range(j, self.max_height):
                        if self.containers[i][k] != 0:
                            result.append(self.containers[i][k])
                    break

        return result

    # 把箱子从move_from位置移动到move_to位置
    def move(self, move_from, move_to):
        self.set_container(move_to, self.get_container(move_from))
        self.set_container(move_from, 0)
        self.height[move_from[0]] -= 1
        self.height[move_to[0]] += 1

    # 打印当前堆场
    def print(self):
        for j in range(self.max_height):
            for i in range(self.total_row):
                print(str(self.containers[i][-j - 1]).rjust(4), end='\t')
            print()

    # 判断两个堆场是否相等:比较所有位置的箱子,如果所有箱子的发箱顺序一样,则相等
    def __eq__(self, other):
        return operator.eq(self.containers, other.containers)

    # 深度复制堆场
    def __copy__(self):
        cloned_containers = [[None for i in range(self.max_height)] for j in range(self.total_row)]
        for i in range(self.total_row):
            for j in range(self.max_height):
                cloned_containers[i][j] = self.containers[i][j]

        return Stack(cloned_containers, self.total_row, self.max_height)


# 代表移动步骤的类
class Step(object):
    def __init__(self, container, move_from, move_to, stack):
        # 被移动的箱子
        self.value = container
        # 箱子移动前的位置
        self.move_from = move_from
        # 箱子移动后的位置
        self.move_to = move_to
        # 堆场
        self.stack = stack
        # 前一个步骤
        self.parent = None
        # 当前是第几步
        self.order = 0
        # 修改堆场状态,实际移动箱子
        self.stack.move(self.move_from, self.move_to)

    # 修改前一个步骤
    def set_parent(self, parent):
        self.parent = parent
        self.order = parent.get_order() + 1

    # 获得前一个步骤
    def get_parent(self):
        return self.parent

    # 获得当前是第几步
    def get_order(self):
        return self.order

    # 获得当前步骤执行后对应的堆场状态
    def get_stack(self):
        return self.stack

    # 启发函数
    def _h(self):
        length = len(self.stack.get_incorrect_containers())
        adjustment = self.value / (self.stack.total_row * self.stack.max_height)
        return length - adjustment

    # 计算从开始状态到当前实际多少步
    def _g(self):
        return self.order

    # 估值函数
    def f(self):
        return self._g() + self._h()

    # 判断两个步骤是否等价:如果对应的堆场状态完全一样,则两个步骤等价
    def __eq__(self, other):
        return operator.eq(self.stack, other.get_stack())

    # tostring的输出格式
    def __str__(self):
        return f'({self.value}: {self.move_from} -> {self.move_to}, {self.order}, {self.f()})'

    # 打印时的表现形式
    def __repr__(self):
        return self.__str__()


if __name__ == '__main__':
    # 最初的堆场状态
    origin = Stack(origin_stack, S, T)
    origin.print()

    open_steps = []
    close_steps = []

    # 将最初堆场的可用步骤加入open list
    open_steps.extend(origin.list_available_steps(close_steps))
    # 根据估值函数最小的原则找到移动的第一步
    current_step = min(open_steps, key=lambda x: x.f())
    # 第一步移动后的堆场状态
    current_stack = current_step.get_stack()

    # 如果移动后还是有摆放位置不对的箱子,则继续调整
    while len(current_stack.get_incorrect_containers()) > 0:
        available_steps = current_stack.list_available_steps(close_steps)
        # A*算法的处理:注意步骤比较会按照Step类的__eq__方法,即比较步骤对应的堆场状态
        for step in available_steps:
            # 如果步骤在open list中,即open list中有其它步骤形成了与可用步骤相同的堆场状态
            if step in open_steps:
                open_step = open_steps[open_steps.index(step)]
                # 与open list中等价步骤的order进行比较,如果原有步骤的order比可用步骤大,则删除原有的步骤, 否则跳过可用步骤
                if open_step.get_order() > step.get_order():
                    open_steps.remove(open_step)
                else:
                    continue

            # 将可用步骤链接到当前步骤之后,然后加入open list
            step.set_parent(current_step)
            open_steps.append(step)

        # 当前步骤被从open list移动到close list
        open_steps.remove(current_step)
        close_steps.append(current_step)

        # 如果已经没有可用的步骤了,输出无法完成,结束程序
        if len(open_steps) == 0:
            print("It can't be done.")
            exit(0)

        # 根据估值函数最小的原则找到移动的下一步
        current_step = min(open_steps, key=lambda x: x.f())
        current_stack = current_step.get_stack()

    # 如果正确退出循环,说明找到了移动方法,倒序打印所有步骤
    actual_steps = [current_step]
    while current_step.get_parent() is not None:
        actual_steps.append(current_step.get_parent())
        current_step = current_step.get_parent()

    for step in reversed(actual_steps):
        print(step)
        step.get_stack().print()

    # 打印步骤数
    print(f'It could be done in {len(actual_steps)} steps.')

  • 9
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

blkq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值