循环与循环双端队列

Posted 却把清梅嗅

tags:

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

循环队列 是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为 环形缓冲器

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

为什么使用循环队列?

这里我们先对 队列 的简单实现进行简单展示,队列应支持两种操作:入队出队

class MyQueue 
    // store elements
    private List<Integer> data;         
    // a pointer to indicate the start position
    private int p_start;            
    public MyQueue() 
        data = new ArrayList<Integer>();
        p_start = 0;
    
    /** Insert an element into the queue. Return true if the operation is successful. */
    public boolean enQueue(int x) 
        data.add(x);
        return true;
    ;    
    /** Delete an element from the queue. Return true if the operation is successful. */
    public boolean deQueue() 
        if (isEmpty() == true) 
            return false;
        
        p_start++;
        return true;
    
    /** Get the front item from the queue. */
    public int Front() 
        return data.get(p_start);
    
    /** Checks whether the queue is empty or not. */
    public boolean isEmpty() 
        return p_start >= data.size();
         

缺点

上面的实现很简单,但在某些情况下效率很低。 随着起始指针的移动,浪费了越来越多的空间。 当我们有空间限制时,这将是难以接受的。

让我们考虑一种情况,即我们只能分配一个最大长度为 5 的数组。当我们只添加少于 5 个元素时,我们的解决方案很有效。 例如,如果我们只调用入队函数四次后还想要将元素 10 入队,那么我们可以成功。

但是我们不能接受更多的入队请求,这是合理的,因为现在队列已经满了。但是如果我们将一个元素出队呢?

实际上,在这种情况下,我们应该能够再接受一个元素。

循环队列 的需求迫在眉睫。

设计思想

这里直接复制了 liweiwei1419 精彩的 题解

1、定义循环变量 front 和 rear 。一直保持这个定义,到底是先赋值还是先移动指针就很容易想清楚了。

front:指向队列头部第 1 个有效数据的位置;
rear:指向队列尾部(即最后 1 个有效数据)的下一个位置,即下一个从队尾入队元素的位置。

(说明:这个定义是依据“动态数组”的定义模仿而来。)

2、为了避免“队列为空”和“队列为满”的判别条件冲突,我们有意浪费了一个位置。

浪费一个位置是指:循环数组中任何时刻一定至少有一个位置不存放有效元素。

判别队列为空的条件是:front == rear;

判别队列为满的条件是:(rear + 1) % capacity == front;。可以这样理解,当 rear 循环到数组的前面,要从后面追上 front,还差一格的时候,判定队列为满。

3、因为有循环的出现,要特别注意处理数组下标可能越界的情况。指针后移的时候,索引 + 1,并且要注意取模。

例题

本文的写作目的就是为了 再次手写一边算法练手

622.设计循环队列

  • 难度:Medium

参考上述设计思路,很容易编写代码,需要注意的是取模代码的编写相对容易出问题。

实现代码:

class MyCircularQueue 

    private int front;
    private int tail;
    private int capacity;
    private int[] arr;

    public MyCircularQueue(int k) 
        this.capacity = k + 1;
        this.arr = new int[capacity];

        this.front = 0;
        this.tail = 0;
    

    public boolean enQueue(int value) 
        if (isFull()) return false;

        arr[tail] = value;
        tail = (tail + 1) % capacity;
        return true;
    

    public boolean deQueue() 
        if (isEmpty()) return false;

        front = (front + 1) % capacity;
        return true;
    

    public int Front() 
        if (isEmpty()) return -1;
        return arr[front];
    

    public int Rear() 
        if (isEmpty()) return -1;
        return arr[(tail - 1 + capacity) % capacity];
    

    public boolean isEmpty() 
        return front == tail;
    

    public boolean isFull() 
        return (tail + 1) % capacity == front;
    

641. 设计循环双端队列

  • 难度:Medium

和上题基本相似,除了多几个取模运算,几乎没什么变化。

class MyCircularDeque 

    private int front;
    private int tail;
    private int capacity;
    private int[] arr;

    public MyCircularDeque(int k) 
        this.capacity = k + 1;
        this.arr = new int[capacity];

        this.front = 0;
        this.tail = 0;
    

    public boolean insertFront(int value) 
        if (isFull()) return false;

        front = (front - 1 + capacity) % capacity;
        arr[front] = value;
        return true;
    

    public boolean insertLast(int value) 
        if (isFull()) return false;

        arr[tail] = value;
        tail = (tail + 1) % capacity;
        return true;
    

    public boolean deleteFront() 
        if (isEmpty()) return false;

        front = (front + 1) % capacity;
        return true;
    

    public boolean deleteLast() 
        if (isEmpty()) return false;

        tail = (tail - 1 + capacity) % capacity;
        return true;
    

    public int getFront() 
        if (isEmpty()) return -1;
        return arr[front];
    

    public int getRear() 
        if (isEmpty()) return -1;
        return arr[(tail - 1 + capacity) % capacity];
    

    public boolean isEmpty() 
        return front == tail;
    

    public boolean isFull() 
        return (tail + 1) % capacity == front;
    

参考 & 感谢

文章绝大部分内容节选自LeetCode

https://leetcode-cn.com/problems/design-circular-queue

https://leetcode-cn.com/problems/design-circular-deque

感谢 liweiwei1419 提供的精彩的 题解

关于我

Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub

如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?

以上是关于循环与循环双端队列的主要内容,如果未能解决你的问题,请参考以下文章

每日一题641. 设计循环双端队列

C++ stl queue(单端队列)和stl deque(双端队列)的区别(与循环队列的区别)

C++ stl queue(单端队列)和stl deque(双端队列)的区别(与循环队列的区别)

641. 设计循环双端队列

循环双端队列-顺序存储

循环双端队列-顺序存储