线性表--队列

Posted Abro.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性表--队列相关的知识,希望对你有一定的参考价值。

目录

前言

一、队列简介

二、队列的顺序存储和链式存储

2.1 队列的基本操作

2.2 队列的顺序存储实现

2.2.1 队列的顺序存储基本描述

2.3 循环队列的顺序存储实现

2.3.1 循环队列的顺序存储基本描述

2.4 队列的链式存储实现

2.4.1 队列的链式存储基本描述

三、队列的应用

总结


前言

队列是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。


一、队列简介

队列(Queue):简称为队,一种线性表数据结构,是一种只允许在表的一端进行插入操作,而在表的另一端进行删除操作的线性表。

队列中允许插入的一端称为队尾;把允许删除的另一端称为队头。当表中没有任何数据元素时,称之为空队。

队列有两种基本操作:插入操作和删除操作

  • 队列的插入操作又称为入队
  • 队列的删除操作又称为出队

简单来说,队列是一种先进先出的线性表,简称为FIFO结构。

可以从两个方面来解释一下队列的定义:

  1. 线性表:队列首先是一个线性表,队列中元素具有前驱后继的线性关系。队列中元素按照a1,a2,a3,...,an的次序依次入队。队头元素为a1,队尾元素为an。
  2. 先进先出原则:根据队列的定义,最先进入队列的元素在队头,最后进入队列的元素在队尾。每次删除的总是队列中的队头元素,即最先进入队列的元素。也就是说,元素进入队列或者退出队列是按照先进先出的原则进行的。

二、队列的顺序存储和链式存储

和线性表类似,队列有两种存储表示方法:顺序存储的队列和链式存储的队列。

  • 顺序存储的队列:利用一组地址连续的存储单元依次存放队列中从队头到队尾的元素,同时使用指针front指向队头元素在队列中的位置,使用指针rear指示队尾元素在队列中的位置。
  • 链式存储的队列:利用单链表的方式来实现队列。队列中元素按照插入顺序依次插入到链表的第一个节点之后,并使用队头指针front指向链表头节点位置,也就是队头元素,rear指向链表尾部位置,也就是队尾元素。

注意:front 和 rear 的指向位置并不完全固定。有时候算法设计上的方便以及代码简洁,也会使 front 指向队头元素所在位置的前一个位置。rear 也可能指向队尾元素在队列位置的下一个位置。具体还是要看算法是如何实现的。

2.1 队列的基本操作

队列的基本操作如下:

  • 初始化空队列:创建一个空队列,定义队列的大小 size,以及队头元素指针 front,队尾指针 rear

  • 判断队列是否为空:当队列为空时,返回 True。当队列不为空时,返回 False。一般只用于队列中删除操作和获取队头元素操作中。

  • 判断队列是否已满:当队列已满时,返回 True,当队列未满时,返回 False。一般只用于顺序队列中插入元素操作中。

  • 插入元素(入队):相当于在线性表最后元素后面插入一个新的数据元素。并改变队列顶指针 top 的指向位置。

  • 删除元素(出队):相当于在线性表最后元素后面删除最后一个数据元素。并改变队列顶指针 top 的指向位置。

  • 获取队列队头元素:相当于获取线性表中最后一个数据元素。与插入元素、删除元素不同的是,该操作并不改变队列顶指针 top 的指向位置。

2.2 队列的顺序存储实现

队列最简单的实现方式就是借助一个数组来描述队列的顺序存储结构。

2.2.1 队列的顺序存储基本描述

这里规定队头指针 self.front 指向队头元素所在位置的前一个位置,而队尾指针 self.rear 指向队尾元素所在位置。

  • 初始化时:创建一个空队列 self.queue,定义队列大小 self.size。令队头指针 self.front 和队尾指针 self.rear 都指向 -1。即 self.front = self.rear = -1
  • 判断队列为空:根据 self.front 和 self.rear 的指向位置关系进行判断。如果对头指针 self.front 和队尾指针 self.rear 相等,则说明队列为空。
  • 判断队列为满:如果 self.rear 指向队列最后一个位置,即 self.rear == self.size - 1,则说明队列已满。
  • 获取队头元素:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 self.front 指向队头元素所在位置的前一个位置,所以队头元素在 self.front 后面一个位置上,返回 self.queue[self.front + 1]
  • 获取队尾元素:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 self.rear 指向队尾元素所在位置,所以直接返回 self.queue[self.rear]
  • 入队操作:先判断队列是否已满,已满直接抛出异常。如果不满,则将队尾指针 self.rear 向右移动一位,并进行赋值操作。此时 self.rear 指向队尾元素。
  • 出队操作:先判断队列是否为空,为空直接抛出异常。如果不为空,则将队头指针 self.front 指向元素赋值为 None,并将 self.front 向右移动一位。

python代码:

class Queue:
    # 初始化空队列
    def __init__(self, size=100):
        self.size = size
        self.queue = [None for _ in range(size)]
        self.front = -1
        self.rear = -1
        
    # 判断队列是否为空
    def is_empty(self):
        return self.front == self.rear
    
    # 判断队列是否已满
    def is_full(self):
        return self.rear + 1 == self.size
    
    # 入队操作
    def enqueue(self, value):
        if self.is_full():
            raise Exception('Queue is full')
        else:
            self.rear += 1
            self.queue[self.rear] = value
            
    # 出队操作
    def dequeue(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            self.front += 1
            return self.queue[self.front]
        
    # 获取队头元素
    def front_value(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            return self.queue[self.front + 1]
    
    # 获取队尾元素
    def rear_value(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            return self.queue[self.rear]

2.3 循环队列的顺序存储实现

在队列的顺序存储实现中,当队列中第 0 ~ size - 1 位置均被队列元素占用时,有 self.rear == self.size - 1,队列已满,再进行入队操作就会抛出队列已满的异常。

此外,由于出队操作总是删除当前的队头元素,将 self.front 进行右移,而插入操作又总是在队尾进行。经过不断的出队、入队操作,队列的变化就像是使队列整体向右移动。当队尾指针 self.rear == self.size - 1 时,此时再进行入队操作就又抛出队列已满的异常。而之前因为出队操作而产生空余位置也没有利用上,这就造成了「假溢出」问题。

为了解决假溢出问题,有两种做法:

  1. 每一次删除队头元素之后,就将整个队列往前移动 1 个位置。
  2. 将队列想象成为头尾相连的循环表,利用数学中的求模运算,使得空间得以重复利用,这样就解决了问题。

2.3.1 循环队列的顺序存储基本描述

这里规定:self.size 为循环队列的最大元素个数。队头指针 self.front 指向队头元素所在位置的前一个位置,而队尾指针 self.rear 指向队尾元素所在位置。

  • 初始化时:创建一个空队列,定义队列大小为 self.size + 1。令队头指针 self.front 和队尾指针 self.rear 都指向 0。即 self.front = self.rear = 0
  • 判断队列为空:根据 self.front 和 self.rear 的指向位置进行判断。根据约定,如果对头指针 self.front 和队尾指针 self.rear 相等,则说明队列为空。
  • 判断队列为满:队头指针在队尾指针的下一位置,即 (self.rear + 1) % self.size == self.front,则说明队列已满。
  • 获取队头元素:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 self.front 指向队头元素所在位置的前一个位置,所以队头元素在 self.front 后一个位置上,返回 self.queue[(self.front + 1) % self.size]
  • 获取队尾元素:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 self.rear 指向队尾元素所在位置,所以直接返回 self.queue[self.rear]
  • 入队操作:先判断队列是否已满,已满直接抛出异常。如果不满,则将队尾指针 self.rear 向右循环移动一位,并进行赋值操作。此时 self.rear 指向队尾元素。
  • 出队操作:先判断队列是否为空,为空直接抛出异常。如果不为空,则将队头指针 self.front 指向元素赋值为 None,并将 self.front 向右循环移动一位。

python代码:

class Queue:
    # 初始化空队列
    def __init__(self, size=100):
        self.size = size + 1
        self.queue = [None for _ in range(size + 1)]
        self.front = 0
        self.rear = 0
        
    # 判断队列是否为空
    def is_empty(self):
        return self.front == self.rear
    
    # 判断队列是否已满
    def is_full(self):
        return (self.rear + 1) % self.size == self.front
    
    # 入队操作
    def enqueue(self, value):
        if self.is_full():
            raise Exception('Queue is full')
        else:
            self.rear = (self.rear + 1) % self.size
            self.queue[self.rear] = value
            
    # 出队操作
    def dequeue(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            self.queue[self.front] = None
            self.front = (self.front + 1) % self.size
            return self.queue[self.front]
        
    # 获取队头元素
    def front_value(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            value = self.queue[(self.front + 1) % self.size]
            return value
        
    # 获取队尾元素
    def rear_value(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            value = self.queue[self.rear]
            return value

2.4 队列的链式存储实现

对于在使用过程中数据元素变动较大,或者说频繁进行插入和删除操作的数据结构来说,采用链式存储结构比顺序存储结构更加合适。

所以我们可以采用链式存储结构来实现队列。我们用一个线性链表来表示队列,队列中的每一个元素对应链表中的一个链节点。然后把线性链表的第 1 个节点定义为队头指针 front,在链表最后的链节点建立指针 rear 作为队尾指针。并且限定只能在链表队头进行删除操作,在链表队尾进行插入操作,这样整个线性链表就构成了一个队列。

2.4.1 队列的链式存储基本描述

这里规定: 队头指针 self.front 指向队头元素所在位置的前一个位置,而队尾指针 self.rear 指向队尾元素所在位置。

  • 初始化时:建立一个链表头节点 self.head,令队头指针 self.front 和队尾指针 self.rear 都指向 head。即 self.front = self.rear = head
  • 判断队列为空:根据 self.front 和 self.rear 的指向位置进行判断。根据约定,如果对头指针 self.front 等于队尾指针 self.rear,则说明队列为空。
  • 获取队头元素:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 self.front 指向队头元素所在位置的前一个位置,所以队头元素在 self.front 后一个位置上,返回 self.front.next.value
  • 获取队尾元素:先判断队列是否为空,为空直接抛出异常。如果不为空,因为 self.rear 指向队尾元素所在位置,所以直接返回 self.rear.value
  • 入队操作:创建值为 value 的链表节点,插入到链表末尾,并令队尾指针 self.rear 沿着链表移动 1 位到链表末尾。此时 self.rear 指向队尾元素。
  • 出队操作:先判断队列是否为空,为空直接抛出异常。如果不为空,则获取队头指针 self.front 下一个位置节点上的值,并将 self.front 沿着链表移动 1 位。如果 self.front 下一个位置是 self.rear,则说明队列为空,此时,将 self.rear 赋值为 self.front,令其相等。

python代码:

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None
        
class Queue:
    # 初始化空队列
    def __init__(self):
        head = Node(0)
        self.front = head
        self.rear = head
    
    # 判断队列是否为空
    def is_empty(self):
        return self.front == self.rear
    
    # 入队操作
    def enqueue(self, value):
        node = Node(value)
        self.rear.next = node
        self.rear = node
    
    # 出队操作
    def dequeue(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            node = self.front.next
            self.front.next = node.next
            if self.rear == node:
                self.rear = self.front
            value = node.value
            del node
            return value
            
    # 获取队头元素
    def front_value(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            return self.front.next.value
        
    # 获取队尾元素
    def rear_value(self):
        if self.is_empty():
            raise Exception('Queue is empty')
        else:
            return self.rear.value

三、队列的应用

队列是算法和程序中最常用的辅助结构,其应用十分广泛。比如现实生活中的排队买票、银行办理业务挂号等等。队列在计算机科学领域的应用主要提现在以下两个方面:

  • 解决计算机的主机与外部设备之间速度不匹配的问题。
  • 解决由于多用户引起的系统资源竞争的问题。

总结

可以利用队列的性质来刷一下力扣的几道题!

622.设计循环队列

225.用队列实现栈

以上是关于线性表--队列的主要内容,如果未能解决你的问题,请参考以下文章

PHP 堆栈和队列

稀疏数组和队列

栈与队列

栈和队列

线性表栈和队列

线性表--优先队列