数据结构与算法之队列

队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d8LEsmtK-1570184756553)(../img/队列/这就是队列.jpg)]

队列的定义

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。与栈相反,队列是一种先进先出(First In First Out, FIFO)的线性表。与栈相同的是,队列也是一种重要的线性结构,实现一个队列同样需要顺序表或链表作为基础。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhGZPmfF-1570184756554)(../img/队列/队列的数据结构.png)]

队列的存储结构

队列的链式存储结构

队列既可以用链表实现,也可以用顺序表实现。跟栈相反的是,栈一般我们用顺序表来实现,而队列我们常用链表来实现,简称为链队列。

我们将队头指针指向链队列的头结点,而队尾指针指向终端结点。(注:头结点不是必要的,但为了方便操作,我们加上了。)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ghsiVfxV-1570184756555)(../img/队列/队列的链式存储结构.png)]

空队列时,front和rear都指向头结点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y9aNzsbn-1570184756556)(../img/队列/空队列.png)]

创建一个队列

创建一个队列要完成两个任务:一是在内存中创建一个头结点,二是将队列的头指针和尾指针都指向这个生成的头结点,因为此时是空队列。

  • 代码实现

    class Node(object):
        """
        定义一个节点
        """
        def __init__(self,item):
            self.data = item
            self.next = None
    
    class Queue(object):
        """
        用链表实现队列
        """
        def __init__(self,node = None):
            self.__head = node
            self.end = None # 用于尾部入列
    
入队列操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qIdqKQB1-1570184756556)(../img/队列/入队列的操作.png)]

  • 代码实现

        def enqueue(self,item):
            """
            入队列,把元素压入队尾
            :param item:
            :return:
            """
            node = Node(item)
            if self.isEmpty():
                self.__head = node
                self.end = node
            else:
                self.end.next = node
                self.end = node
    
出队列操作

出队列操作是将队列中的第一个元素移出,队头指针不发生改变,改变头结点的next指针即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qab09HyY-1570184756557)(../img/队列/出队列的操作.png)]

如果原队列只有一个元素,那么我们就应该处理一下队尾指针。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t0TEE9Zf-1570184756557)(../img/队列/出队列的操作2.png)]

  • 代码实现

        def dequeue(self):
            """
            出队列,讲队首元素弹出
            :return:
            """
            if self.isEmpty():
                print("队列为空!!!")
            else:
                end = self.__head.data
                self.__head = self.__head.next
                return end
    
判断队列是否为空
  • 代码实现

        def isEmpty(self):
            """
            判断队列是否为空
            :return:
            """
            return self.__head == None
    
    
返回队列的长度
  • 代码实现

        def size(self):
            """
            判断队列的长度
            :return:
            """
            # cur 游标,用来移动遍历节点
            cur = self.__head
            # count记录数量
            count = 0
            while cur != None:
                count += 1
                cur = cur.next
            return count
    
测试
  • 代码实现

    if __name__ == '__main__':
    
        # 实例化队列
        que = Queue()
    
        # 判断队列是否为空
        print("队列是否为空:%s" % que.isEmpty())# 判断队列是否为空:True
    
        # 入队列
        que.enqueue(1)
        que.enqueue(2)
        que.enqueue(3)
        que.enqueue(4)
    
        # 判断队列是否为空
        print("判断队列是否为空:%s" % que.isEmpty())# 判断队列是否为空:False
    
        # 查看队列大小
        print("队列大小:%s" % que.size())# 队列大小:4
    
        # 查看队尾元素
        print("队尾元素:%s" % que.getRear())# 队尾元素:4
    
        # 查看队首元素
        print("队首元素:%s" % que.getFront())# 队首元素:1
    
        # 出队列
        print("当前出队列元素:%s" % que.dequeue())# 当前出队列元素:1
        print("当前出队列元素:%s" % que.dequeue())# 当前出队列元素:2
        print("当前出队列元素:%s" % que.dequeue())# 当前出队列元素:3
        print("当前出队列元素:%s" % que.dequeue())# 当前出队列元素:4
    
        # 判断队列是否为空
        print("队列是否为空:%s" % que.isEmpty())# 队列是否为空:True
    

队列的顺序存储结构

我们假设一个队列有n个元素,则顺序存储的队列需建立一个大于n的存储单元,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端则是队头。

使用列表实现一个队列
  • 代码实现

    class Queue(object):
        """
        队列
        """
        def __init__(self):
            """
            创建空的队列
            """
            self.items = []
    
判断是否为空
  • 代码实现

        def isEmpty(self):
            """
            判断队列是否为空
            :return:
            """
            return self.items == []
    
入队列操作

入队列操作其实就是在队尾追加一个元素,不需要任何移动,时间复杂度为O(1)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lwSow4fd-1570184756558)(../img/队列/入队列的操作(顺序存储结构)].png)

  • 代码实现

        def enqueue(self,item):
            """
            入队列
            :param item: 进队列的元素
            :return:
            """
            self.items.append(0,item)
    
出队列操作

出队列则不同,因为我们已经架设下标为0的位置是队列的队头,因此每次出队列操作所有元素都要向前移动。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pBQ5kLlA-1570184756558)(../img/队列/出队列的操作(顺序存储结构)].png)

  • 代码实现

        def dequeue(self):
            """
            出队列
            :return:
            """
            return self.items.pop()
    
返回队列的大小
  • 代码实现

        def size(self):
            """
            队列的大小
            :return:
            """
            return len(self.items)
    
测试
if __name__ == '__main__':
    # 实例化队列
    queue = Queue()

    # 判断队列是否为空
    print(queue.isEmpty()) # True

    # 入队列
    queue.enqueue(1)
    queue.enqueue(2)
    queue.enqueue(3)
    queue.enqueue(4)

    # 查看队列的大小
    print(queue.size())# 4

    # 判断队列是否为空
    print(queue.isEmpty())

    # 出队列
    print(queue.dequeue())# 4
    print(queue.dequeue())# 3
    print(queue.dequeue())# 2
    print(queue.dequeue())# 1

 
    # 判断队列是否为空
    print(queue.isEmpty())

循环队列

循环队列它的容量是固定的,并且它的队头和队尾指针都可以随着元素入出队列而发生改变,这样循环队列逻辑上就好像是一个环形存储空间。队列的存储结构中使用的最多的是循环队列。循环队列包括两个指针, front 指针指向队头元素, rear 指针指向队尾元素的下一个位置。

  • 队列为空的判断条件是:front == rear

  • 队列满的判断条件是:(rear+1)%maxsize == front

  • 队列长度的计算公式:(rear-front+maxsize)%maxsize

但要注意的是,在实际的内存当中,不可能有真正的环形存储区,我们只是用顺序表模拟出来的逻辑上的循环。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SOXfaLHY-1570184756559)(../img/队列/循环队列定义.png)]

创建一个循环队列
  • 代码实现

    class loopqueue(object):
        def __init__(self, capacity=10):
            """
            构造函数
            :param maxsize: 队列默认的容量为 10
            """
            self.maxsize = capacity + 1  # 队列的初始容量为capacity,对于内部实现,需要满足队列判空与判满的奇异性,
            # 当self.rear + 1 = self.front时,即队列判定为满,此时还剩余一个空间
            self.front = 0  # 队首索引
            self.rear = 0  # 队尾索引
            self.data = [None] * self.maxsize  # 初始化队列的容量
    
判断循环队列是否为空
  • 代码实现

        def isEmpty(self):
            """
            判断队列是否为空
            :return:
            """
            return self.front == self.rear  # 判空
    
当前循环队列的大小
  • 代码实现

        def Capacity(self):
            """
            当前循环队列的大小
            :return:
            """
            return self.maxsize - 1  # 有效元素个数
    
查看队列的大小
  • 代码实现

        def size(self):
            """
            循环队列中有效元素的个数
            :return:
            """
            resize = None  # 要返回的size
            if self.rear >= self.front:  # 队尾的索引在队首的索引后面(包含等于)
                resize = self.rear - self.front
            else:
                resize = self.maxsize - (self.front - self.rear)
            return resize
    
入队列操作
  • 代码实现

        def enqueue(self, item):
            """
            入队列操作
            :param item:
            :return:
            """
            if (self.rear + 1) % self.maxsize == self.front:  # 队列已满
                self._resize(self.Capacity() * 2)  # 队列有效容量扩大两倍
            self.data[self.rear] = item  # 将item放到 self.rear
            self.rear = (self.rear + 1) % self.maxsize  # 维护self.rear,循环队列 要对全体空间取余
    
    
出队列操作
  • 代码实现

        def dequeue(self):
            """
            循环队列出队
            :return:
            """
            if self.isEmpty():
                raise Exception("This queue is empty!!!")
            ret_value = self.data[self.front]  # 记录队首元素
            self.data[self.front] = None  # 将队首元素值空
            # 维护self.front 直接加1,要对全体空间取余
            self.front = (self.front + 1) % self.maxsize
            if self.size() and self.maxsize // self.size() == 4:  # 队列不为空且有效元素个数为可容纳元素的四分之一时,缩容
                self._resize(self.Capacity() // 2)  # 缩容为原先的二分之一
            return ret_value
    
查看队首元素
  • 代码实现

        def getFront(self):
            """
            获取队首元素
            :return:
            """
            if self.isEmpty():
                raise Exception("This queue is empty!!!")
            return self.data[self.front]
    
遍历循环队列
  • 代码实现

        def travel(self):
            """
            遍历循环队列的有效元素
            :return:
            """
            print("loopqueue:Front-- ", end="")
            index = self.front  # 从队首开始
            while index != self.rear:  # 没有到队尾一直遍历
                if index + 1 != self.rear:
                    print(self.data[index], end=" ")  # 打印当前元素
                    index = (index + 1) % self.maxsize
                    # index向后推进,注意是循环队列,到self.data的尾部就要返回到0索引处哦,
                    # 所以要对真实的存储空间取余,而不是对self.Capacity()取余!这么做就错了!
                else:
                    print(self.data[index], end=" ")
                    break
            print("--rear")
            print("size : %d , Capacity : %d" % (self.size(), self.Capacity()))
    
    
队列扩/缩容
  • 代码实现

        def _resize(self, capacity):
            """
            循环队列扩/缩容(真正的self.maxsize应该是capacity+1,才能容纳capacity这么多元素)
            :param capacity:
            :return:
            """
            # 此时不能先做self.maxsize = capacity + 1 。因为后面要将当前队列中的元素全部取出来,一旦
            # self.rear在self.front的前面,而self.maxsize已经改变,就不能全部取出来了!好好想一下~以前就进过坑
    
            tmp_list = [None] * (capacity + 1)
            index = self.front  # 准备开始遍历原先的self.data,转移元素!从self.front开始
            while index != self.rear:  # 若index一直没到self.rear,就继续往后
                # 注意在这里我把原先的元素都放到新list的以索引零开始的地方顺序的放置元素
                tmp_list[index - self.front] = self.data[index]
                index = (index + 1) % self.maxsize  # index往后移,注意循环队列的性质。
            self.data = tmp_list  # 更新self.data为新的那个数据,tmp_list会被自动垃圾回收的,不用担心它。
    
            self.rear = self.size()  # 维护self.rear。就是 0 + self.size()。因为转移元素并不改变size
            self.front = 0  # 维护self.front,因为我是从零开始放的,所以置零。
    
            self.maxsize = capacity + 1  # 最后再维护self.maxsize!讲过了,前面的很多操作依赖于原先的最大容量,等他们都ok了,最后维护这个
    
    
    
测试
  • 代码实现

    if __name__ == '__main__':
        # 实例化循环队列
        lq = loopqueue()
    
        # 判断循环队列是否为空
        print(lq.isEmpty())
    
        # 往队列添加元素
        print("入队18次")
        for i in range(18):
            lq.enqueue(i)
    
        # 判断循环队列是否为空
        print(lq.isEmpty())
    
        # 查看对首元素
        print("front:", lq.front)
    
        # 查看队尾元素
        print("rear:", lq.rear)
    
        # 遍历队列有效元素
        print("遍历队列有效元素:", end=" ")
        lq.travel()
    
        # 出队列
        print("出队15次")
        docker = []
        for i in range(15):
            docker.append(lq.dequeue())
            print("steps : %d, front : %d, rear: %d, size: %d, capacity: %d " %
                  (i + 1, lq.front, lq.rear, lq.size(), lq.Capacity()))
    
        # 查看出队列元素
        print("出队元素:", docker)
        print("队列元素打印:", end="")
    

双端队列

双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双端队列可以在队列任意一端入队和出队。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L9W2Z6cZ-1570184756560)(…/img/队列/双端队列.png)]

创建一个双端队列
  • 代码实现

    class Deque(object):
        """双端队列"""
        def __init__(self):
            self.items = []
    
判断队列是否为空
  • 代码实现

        def is_empty(self):
            """
            判断队列是否为空
            :return:
            """
            return self.items == []
    
入队列操作(队首)
  • 代码实现

        def add_front(self, item):
            """
            入队列操作(队首)
            :param item:
            :return:
            """
            self.items.insert(0,item)
    
入队列操作(队尾)
  • 代码实现

        def add_rear(self, item):
            """
            入队列操作(队尾)
            :param item:
            :return:
            """
            self.items.append(item)
    
出队列操作(队首)
  • 代码实现

        def remove_front(self):
            """
           出队列操作(队首)
            :return:
            """
            return self.items.pop(0)
    
出队列操作(队尾)
  • 代码实现

        def remove_rear(self):
            """
            出队列操作(队尾)
            :return:
            """
            return self.items.pop()
    
返回队列大小
  • 代码实现

        def size(self):
            """
            返回队列大小
            :return:
            """
            return len(self.items)
    
测试
  • 代码实现

    if __name__ == "__main__":
        # 实例化双端队列
        deque = Deque()
    
        # 判断队列是否为空
        print(deque.is_empty()) # True
    
        # 入队列操作(队首)
        deque.add_front(1)
        deque.add_front(2)
        # 入队列操作(队尾)
        deque.add_rear(3)
        deque.add_rear(4)
    
        # 判断队列是否为空
        print(deque.is_empty())
        
        # 查看队列大小
        print(deque.size()) # 4
        
        # 出队列操作(队首)
        print(deque.remove_front()) # 2
        print(deque.remove_front()) # 1
        
        # 出队列操作(队尾)
        print(deque.remove_rear()) #  4
        print(deque.remove_rear()) # 3
        
        # 判断队列是否为空
        print(deque.is_empty()) # True
    
  

## 参考资料

Annihilation7的博客:https://blog.csdn.net/Annihilation7/article/details/82847774

小甲鱼数据结构与算法:https://www.bilibili.com/video/av2975983/?p=30

 入队列操作(队首)
      deque.add_front(1)
      deque.add_front(2)
      # 入队列操作(队尾)
      deque.add_rear(3)
      deque.add_rear(4)
  
      # 判断队列是否为空
      print(deque.is_empty())
      
      # 查看队列大小
      print(deque.size()) # 4
      
      # 出队列操作(队首)
      print(deque.remove_front()) # 2
      print(deque.remove_front()) # 1
      
      # 出队列操作(队尾)
      print(deque.remove_rear()) #  4
      print(deque.remove_rear()) # 3
      
      # 判断队列是否为空
      print(deque.is_empty()) # True

参考资料

Annihilation7的博客:https://blog.csdn.net/Annihilation7/article/details/82847774

小甲鱼数据结构与算法:https://www.bilibili.com/video/av2975983/?p=30

数据结构与算法全套精讲:https://www.bilibili.com/video/av50524094/?p=16

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值