队列

队列也是一种容器,任何时候可访问的元素为最早存入队列而未出队的元素,队列是先进先出结构。

队列的实现

队列抽象数据结构

ADT Queue:
    Queue(self)              #创建空队列
    is_empty(self)           #判断是否为空
    enqueue(self,elem)       #元素elem入队
    dequeue(self)            #删除入队最早的元素,并返回
    peek(self)               #查看入队最早的元素,不删除

链接表实现

考虑到队列需要两端的高效操作,所以用带尾端指针的单链表来实现队列,尾端插入时间为 O(1) O ( 1 ) ,首端访问和删除也为 O(1) O ( 1 ) ;入队操作把元素加在链接表的尾端,出队操作在链接表的首端获取元素。

class Queue:
    def __init__(self):
        self._first = None
        self._last = None

    def is_empty(self):
        return self._first is None

    def enqueue(self, elem):
        n = LNode(elem)
        if self._first is None:
            self._first, self._last = n, n
        else:
            self._last.next, self._last = n, n

    def dequeue(self):
        if self._first is None:
            raise QueueUnderflow("The Queue is empty!")
        elif self._first == self._last:
            m = self._first
            self._first, self._last = None, None
            return m.elem
        else:
            m = self._first
            self._first = self._first.next
            return m.elem

    def peek(self):
        if self._first is None:
            raise QueueUnderflow("The Queue is empty!")
        else:
            return self._first.elem

顺序表实现

根据顺序表的性质,不管哪边作为队列头部,入队出队总会有一个为 O(n) O ( n ) 操作,效率不是很理想。

循环顺序表

将顺序表看作一种环形结构,首尾相连,队列元素保存在环状结构中的一段。
顺序队列
- q.elems q . e l e m s 始终指向表的开始。
- q.head q . h e a d 指向队列的首元素, q.rear q . r e a r 指向队列结束的第一个空位。
- 队列元素保存在顺序表的一块连续单元里 [q.head:q.rear) [ q . h e a d : q . r e a r ) ,两个变量对表长取模的和为队列长度。
- q.head q . h e a d q.rear q . r e a r 相同时队列为空;当表中位置都有元素时, q.head q . h e a d q.rear q . r e a r 也相同,但是队列已经满了,所以留一个空位出来,把 (q.rear+1) ( q . r e a r + 1 ) % q.len==q.head q . l e n == q . h e a d 时定义为队列满。
出入队的操作如下:

q.head = (q.head + 1) % q.len
q.rear = (q.rear + 1) % q.len
队列的list实现

由于表空而无法出队的异常:

class QueueUnderflow(ValueError):
    pass

队列SQueue的基本设计:
- SQueue对象里用一个list类型的成分_elems存放队列元素。
- 用两个属性_head和_num分别记录队列首元素所在位置的下标和表中元素个数。
- 用list作为存储区,需要检查当前表是否已满,必要时换一个存储表,因此要记录当前表的长度_len。

class SQueue:
    def __init__(self, init_len=8):
        self._len = init_len           #存储区首次默认长度为8
        self._elems = [0] * init_len   #存储区为8个[0]
        self._head = 0                 #首元素位置
        self._num = 0                  #元素总数

    def is_empty(self):
        return self._num == 0

    def peek(self):
        if self._num == 0:
            raise QueueUnderflow
        return self._elems[self._head]

    def dequeue(self):
        if self._num == 0:
            raise QueueUnderflow
        e = self._elems[self._head]
        self._head = (self._head + 1) % self._len  #出队时首元素链接后移一位,可能会超过储存区长度,所以进行取模运算
        self._num -= 1
        return e

    def enqueue(self, e):
        if self._num == self._len:
            self.__extend()
        self._elems[(self._head+self._num) % self._len] = e  
        self._num += 1

    def __extend(self):
        old_len = self._len
        self._len *= 2
        new_elems =[0]*self._len
        for i in range(old_len):  #把原存储区的元素按顺序从_head开始存入新存储区
            new_elems[i] = self._elems[(self._head+i)%old_len]
        self._elems, self._head = new_elems, 0

迷宫求解

迷宫
迷宫问题的一般思路为:
- 从迷宫入口开始检查,这是初始位置。
- 如果当前位置为出口,则问题已解决。
- 从可行的方向选取一个方向,继续探寻,如果无路可走就返回前一个路口,选取下一个方向继续。
现在考虑一种简单形式的迷宫,可以直接映射到二维的 0/1 0 / 1 矩阵,空位置用0表示,障碍和边界用1表示;这里需要保存已经发现但尚未探索的分支信息,首先考虑使用栈或队列,采用不同的缓存结构,会对所实现的搜索过程有重要影响:
- 按栈的方式保存和使用信息,实现的探索过程是每步选择一种可能方向一直前进,直到无法前进才退回到此前最后选择点,换路径继续该过程。
- 按队列方式保存和使用信息,就是总从最早遇到的搜索点不断拓展。

问题表示和辅助结构

选择合适的数据表示,迷宫本身可以用一个元素值为 0/1 0 / 1 的矩阵表示,在 python p y t h o n 里可以用以 list l i s t 为元素的 list l i s t 表示,迷宫入口和出口各用一对下标表示。
为了防止搜索时在某个位置绕圈子,程序运行中必须记录已探查过的位置,在搜索过程中不断检查有关记录,确保不重复。在搜索过程中把检查过的位置标记为 2 2 ,计算中发现元素值为非0就是不能通行。
对于单元 (i,j) ( i , j ) ,它的四个相邻位置分别为: (i1,j) ( i − 1 , j ) (i,j+1) ( i , j + 1 ) (i+1,j) ( i + 1 , j ) (i,j1) ( i , j − 1 ) 。为了方便得到四个位置,这里定义一个二元组的表:

dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]

对于任何一个位置 (i,j) ( i , j ) ,给它加上dirs[0],dirs[1],dirs[2],dirs[3],就分别得到了该位置的四个相邻位置。
另外两个辅助函数:
maze m a z e 的位置 pos p o s 2 2 表示已经检查过。

def mark(maze, pos):
    maze[pos[0]][pos[1]] = 2

检查迷宫maze的位置 pos p o s 是否可行。

def passable(maze, pos):
    return maze[pos[0]][pos[1]] == 0
求解迷宫
递归求解

因为 python p y t h o n 解释器为支持递归程序采用了运行栈,所以直接递归求解:
- mark m a r k 当前位置
- 检查当前位置是否为出口,如果是,则成功结束
- 逐个检查当前位置的四邻是否可以通达出口
- 如果四周的探索都失败,报告失败

maze = [
        [1,1,1,1,1,1,1,1,1,1,1,1,1,1],
        [1,0,0,0,1,1,0,0,0,1,0,0,0,1],
        [1,0,1,0,0,0,0,1,0,1,0,1,0,1],
        [1,0,1,0,1,1,1,1,0,1,0,1,0,1],
        [1,0,1,0,0,0,0,0,0,1,1,1,0,1],
        [1,0,1,1,1,1,1,1,1,1,0,0,0,1],
        [1,0,1,0,0,0,0,0,0,0,0,1,0,1],
        [1,0,0,0,1,1,1,0,1,0,1,1,0,1],
        [1,0,1,0,1,0,1,0,1,0,1,0,0,1],
        [1,0,1,0,1,0,1,0,1,1,1,1,0,1],
        [1,0,1,0,0,0,1,0,0,1,0,0,0,1],
        [1,1,1,1,1,1,1,1,1,1,1,1,1,1]
        ]

#递归的方法如下:
def find_path(maze, pos, end):
    mark(maze, pos)   #首先改变该点数值
    if pos == end:    #判断该点是否为终点
        print "the end is:", pos
        return True
    for i in range(4):   #迭代改点周围的四个点
        nextp = [pos[0] + dire[i][0], pos[1] + dire[i][1]]
        if passable(maze, nextp):   #判断是否可以走
            if find_path(maze, nextp, end):   #递归自身
                print "the end is:", pos
                return True
    return False
回溯法求解

从入口开始搜索,遇到分支记录信息,然后继续探查,如果没有出口就回溯到分支处探查下一个方向。搜索中位置入栈有两种情况:1,把从入口到当前位置途径的所有位置入栈;2,只在栈里保存分支节点的未探查方向。
上图的迷宫中还有一种情况,环形路线,开始时一个节点有两个方向,顺着一个走到最后时,该节点不再存在未探查方向了;算法最后还要输出正确路径的所有位置。

入口start相关信息(位置和尚未探索的方向)入栈:
    while 栈不空:
        弹出栈顶元素作为当前位置继续搜索:
        while 当前位置存在未探索方向:
            求出下一探索位置nextp
            if nextp 是出口:
                输出路径并结束
            if nextp 尚未探索:
                将当前位置和nextp顺序入栈并退出内层循环

入栈用序对 (pos,nxt) ( p o s , n x t ) 表示, pos p o s 为坐标, nxt n x t 为整数,表示回溯到该位置的下一探索方向,为 dirs d i r s 的下标。

def maze_solver(maze, start, end):
    if start == end:
        print start
        return
    st = SStack()
    mark(maze, start)
    st.push((start, 0))
    while not st.is_empty():
        pos, nxt = st.pop()
        for i in range(nxt, 4):   #在for中依次循环当前位置的上下左右四个方向,如果有一个方向可以passable的话,把当前位置余下的方向入栈,再把这次探查的方向入栈,回到while循环,
                                  #每次向前走一步就会把当前位置余下的探索方向压入栈;由于每次都mark了当前位置,所以回溯时,while每次取出栈中的非分支点都不会通过passable
                                  #(非分支点只有前后两个方向,路过后前后都会改变标记),每次都可以回溯到分支节点。
            nextp = (pos[0] + dirs[i][0], pos[1] + dirs[i][1])
            if nextp == end:
                print_path(end, pos, st)
                return
            if passable(maze, nextp):
                st.push((pos, i+1))
                mark(maze, nextp)
                st.push((nextp, 0))
                break
    print "No path found."
队列求解

队列有先进先出的特性,每次把未探查的位置入队,再从队中取出检查相邻位置,未探查的位置再入队。
基本框架:

start标记
start入队
while 队列中有未充分探查的位置:
    取出一个位置pos
    检查pos的相邻位置
        遇到end成功结束
        尚未探查的都mark并入队
队列为空,搜索失败
def maze_solver_queue(maze, start, end):
    if start == end:
        print "Path finds."
        return
    qu = SQueue()
    mark(maze, start)
    qu.enqueue(start)
    while not qu.is_empty():
        pos = qu.dequeue()
        for i in range(4):
            nextp = (pos[0] + dirs[i][0], pos[1] + dirs[i][1])
            if passable(maze, nextp):
                if nextp == end:
                    print "Path find."
                    return
                mark(maze, nextp)
                qu.enqueue(nextp)
    print "No path."
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值