数据结构: 队列 讲解

数据结构: 队列 讲解

一、概念:

先进者先出,这就是 队列。

把它想象成排队买票,先来的先买,后来的人只能站末尾,不允许插队。

队列跟栈非常相似,支持的操作也很有限,最基本的操作也是两个:

  • 入队 enqueue(),放一个数据到队列尾部;
  • 出队 dequeue(),从队列头部取一个元素;

所以,队列跟栈一样,也是一种操作受限的线性表数据结构

二、顺序队列和链式队列:

跟栈一样,队列可以用数组来实现,也可以用链表来实现。

  • 用数组实现的队列叫作顺序队列
  • 用链表实现的队列叫作链式队列

1.顺序队列:

队列,需要两个指针,一个head 指针,指向队头;一个tail 指针,指向队尾

class SqQueue(object):
    """顺序队列"""

    def __init__(self, size):
        """
        size:指定队列的大小
        """
        self.data = list(None for _ in range(size + 1))
        self.max_size = size + 1
        self.head = 0
        self.tail = 0
        self.length = 0

    def get_length(self):
        """获取队列的长度"""
        return self.length

    def is_full(self):
        """判断队列是否满了"""
        if (self.tail + 1) % self.max_size == self.head:
            return True
        return False

    def is_empty(self):
        """判断队列是否为空"""
        if self.head == self.tail:
            return True
        return False

    def en_queue(self, element):
        """进入队列, 从队尾插入"""
        if self.is_full():
            raise IndexError('Queue is full!')
        else:
            self.data[self.tail] = element
            self.tail = (self.tail + 1) % self.max_size
            self.length += 1

    def de_queue(self):
        """出队列, 从对头取出"""
        if self.is_empty():
            raise IndexError('Queue is empty!')
        else:
            del_element = self.data[self.head]
            self.data[self.head] = None
            self.head = (self.head + 1) % self.max_size
            self.length -= 1
            return del_element

    def show_queue(self):
        """显示队列元素, 从队首开始显示"""
        if self.is_empty():
            raise IndexError('Queue is empty!')
        else:
            j = self.head
            while j != self.tail:
                print(self.data[j])
                j = (j + 1) % self.max_size


queue = SqQueue(5)
for i in range(5):
    queue.en_queue(i)

queue.show_queue()
print('----------')
queue.de_queue()
queue.show_queue()

如图所示

  1. 当a、b、c、d依次入队之后,队列中的 head 指针,指向下标为 0 的位置,tail 指针指向下标为 4 的位置。

  2. 当调用两次出队操作后,队列中 head 指针指向下标为 2 的位置, tail 指针仍然指向下标为 4 的位置。

随着不停的进行入队、出队操作,head 和 tail 都会持续往后移动。当tail 移动最右边,即使数组中还有空间,也无法继续往队列中添加数据了。

  • 如果使用数据迁移,每次进、出队都相当于删除数组下标为 0 的数据,要搬移整个队列中的数据,这样出队操作的时间复杂度从原来的O(1) 变为了 O(n)

    扫描二维码关注公众号,回复: 8489345 查看本文章
  • 优化思路,在出队时,不用搬移数据,如果没有空闲空间,在入队时,在集中出发依次数据的搬移。

2.链式队列:

链表的实现,我们同样需要两个指针:head 指针和 tail 指针。它们分别指向链表的第一个结点和最后一个结点

class Node(object):
    """节点"""
    def __init__(self, data=None):
        self.data = data
        self.next = None


class LkQueue(object):
    """链式队列"""

    def __init__(self):
        self.front = Node()
        self.rear = Node()
        self.length = 0

    def get_length(self):
        """获取长度"""
        return self.length

    def is_empty(self):
        """判断是否为空"""
        if self.length == 0 :
            return True
        return False

    def en_queue(self, elem):
        """入队操作"""

        tmp = Node(elem)
        if self.is_empty():
            self.front = tmp
            self.rear = tmp
        else:
            self.rear.next = tmp
            self.rear = tmp
        self.length += 1

    def de_queue(self):
        """出队操作"""
        if self.is_empty():
            raise ValueError("LKQueue is empty!")
        else:
            del_elem = self.front.data
            self.front = self.front.next
            self.length -= 1
            return del_elem

    def show_queue(self):
        """显示队列"""
        if self.is_empty():
            raise ValueError("LKQueue is empty!")

        j = self.length
        tmp = self.front
        while j > 0:
            print(tmp.data)
            tmp = tmp.next
            j -= 1


lkq = LkQueue()
for i in range(5):
    lkq.en_queue(i)

lkq.show_queue()
print('------------------')
lkq.de_queue()
lkq.show_queue()

如图所示

3.循环队列:

我们把首尾相连,扳成一个

如图所示

  1. 队列的大小为8,当前 head = 4,tail = 7;

  2. 当一个新元素 a 入队时,我们放入小标 为 7 的位置;

  3. 这时,我们不把 tail 更新为 8,而是在环中后移一位,到下标 0 的位置;

  4. 在有一个元素 b 入队时, 我们将 b 放入下标为 0 的位置,然后tail 加 1 更新为 1 ;

  5. 在 a,b 依次入队后,循环队列中元素就变成下面这样;

代码实现

class CircularQueue(object):
    """循环队列"""

    def __init__(self, size):
        """设置队列长度为size"""
        
        self.queue = [""] * size
        self.max_size = size
        self.start = -1
        self.end = -1

    def is_full(self):
        """检查循环队列是否已满"""
        if (self.end + 1) % self.max_size == self.start:
            return True
        return False

    def is_empty(self):
        """检查循环队列是否为空"""
        if self.start == -1 and self.end == -1:
            return True
        return False

    def front(self):
        """从队首获取元素, 如果队列为空, 返回 -1"""
        return -1 if self.is_empty() else self.queue[self.start]

    def rear(self):
        """获取队尾元素, 如果队列为空, 返回 -1"""
        return -1 if self.is_empty() else self.queue[self.end]

    def en_queue(self, element):
        """向循环队列插入一个元素, 如果成功插入返回 True"""
        if not self.is_full():
            self.start = 0
            self.end = (self.end + 1) % self.max_size
            self.queue[self.end] = element
            return True
        return False

    def de_queue(self):
        """从循环队列中删除一个元素, 如果成功删除, 返回True"""
        if not self.is_empty():
            if self.start == self.end:
                self.start, self.end = -1, -1
            else:
                self.start = (self.start + 1) % self.max_size
            return True

        return False


queue = CircularQueue(8)
for i in range(8):
    queue.en_queue(i)

4.阻塞队列:

阻塞队列就是在队列基础上增加了阻塞操作

简单理解

  • 就是在队列为的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;
  • 如果队列已经了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回;

如图所示

也可以想到"生产者" 跟 “消费者” 的个数,来提高数据的处理效率。

如图所示

在多线程的情况下,有多个线程同时操作队列,这个时就会存在线程安全的问题,如何实现一个线程安全队列呢?

5.并发队列:

线程安全的队列,我们叫做并发队列.

最简单方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。

参考资料:

《数据结构与算法之美》

发布了147 篇原创文章 · 获赞 170 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Fe_cow/article/details/103786538
今日推荐