设计:循环双端队列

JDK的deque接口

JDK1.6提供了双向队列的接口,它规定了一种双向队列的行为例如:头部插入/删除、尾部插入/删除。如果我们仅仅看deque接口的话(不看collection继承下来的方法),你会发现提供的方法都是基于头尾的
这个特点可以直接用于实现栈与队列
Deque接口继承自queue接口,特提供了更多指明方向的方法例如offerFirst、offerLast,如果仅使用Queue接口去接收一个实现类(如LinkedList),那么默认的先进先出就是尾插头删,而deque既然是双端队列接口,那么不论是头还是尾都可以实现先进先出的特点

deque有四个常用实现类:
【1】linkedList,基于双向队列实现
【2】arrayDeque,基于循环数组实现
【3】concurrentLinkedDeque,线程安全,非阻塞
【4】linkedBlockingDeque,线程安全,空队列获取元素时阻塞

实现循环双端队列

力扣地址
我们要设计一个循环的双端队列,有以下特点:
【1】满了就不能再插入了。(不是无限插入的,有上限值)
【2】空了不能够获得。
(上面直接return false,如果放在阻塞队列则应该被阻塞直到队列不满或不空)

基于双向链表实现

其实本题更适合使用循环数组实现,双向链表实现比较简单,就是常规的双向链表,并且通过首尾指针进行一些查询、删除、插入操作。(注意:本题的容量是限定死的,不是无限插入的)

    class Node{
        Node pre;
        Node next;
        int val;
        public Node(int val) {
            this.val = val;
        }
    }

如果使用链表实现,其实问题退化为了设计链表,并实现一些头插尾插的方法,都是O(1),但是需要额外维护链表的成员头结点和尾结点。

    int size;
    int cap;
    Node head;
    Node tail;
    public MyCircularDeque(int k) {
        cap=k;
        head=new Node(-1);
        tail=new Node(-1);
        head.next=tail;
        tail.pre=head;
    }
    public boolean insertFront(int value) {
        if(isFull())return false;
        Node node = new Node(value);
        node.next=head.next;
        head.next.pre=node;
        head.next=node;
        node.pre=head;
        size++;
        return true;
    }

拿到首元素,然后进行插入操作,需要同时维护next和pre的关系。其中insertLast和insertFront代码基本就是把头结点换成尾结点,不再额外展示。

    public boolean deleteFront() {
        if(isEmpty())return false;
        head.next=head.next.next;
        head.next.pre=head;
        size--;
        return true;
    }

删除操作,无非就是通过哨兵节点拿到首节点或尾结点,然后转接相应的pre和next关系

    public int getFront() {
        if(isEmpty())return -1;
        return head.next.val;
    }
    public int getRear() {
        if(isEmpty())return -1;
        return tail.pre.val;
    }

    public boolean isEmpty() {
        return size==0;
    }

    public boolean isFull() {
        return size == cap;
    }

本题更令人满意的实现还是基于循环数组。

基于循环数组实现

因为我们要实现的是双端队列,不管插入还是删除操作,都仅发生在队列的首部或者尾部,因此使用数组实现效率甚至更高(因为不需要创建和删除节点对象,仅需要修改某个位置的字面量)。
比起基于链表的实现中,通过修改链表节点的指针,使得被删除节点对象称为“不可达对象”,数组更新像是通过移动指针实现的逻辑删除

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

底层使用固定大小的数组,并且使用双指针限定数组的访问范围,[front…rear]之间就是不可用的位置,而[rear…front]之间则是剩余的位置。

    public MyCircularDeque(int k) {
        capacity = k + 1;
        arr = new int[capacity];
        front = 0;
        rear = 0;
    }

这里k+1是一个核心,这里通过使用一个哨兵位来区分空和满两种状态

    public boolean isEmpty() {
        return front == rear;
    }

我们使用两个指针重合,用于判断队列为空。
注意两个指针移动的规则:
【1】初始时,两个指针重合
【2】当插入一个元素,arr[rear++]=x 。 也就是说插入完毕后,rear指针会指向队尾(即最新插入的元素)元素的下一个位置
【3】当删除一个元素时则是front++,因此front总是指向队首(最晚插入的元素)(除了初始阶段)

    public boolean isFull() {
        return (rear + 1) % capacity == front;
    }

因此,如果当前状态是满,那么rear此时的位置便是哨兵位,加一取模后将与front指针重合再次(这也是为什么要单独设置哨兵位的原因)

本题的核心就是通过设置哨兵位区别满和空两种状态,其他的方法的实现都很简单。(注意,循环数组实现的循环队列中,每个位置都是相对的,包括哨兵位)

    public boolean insertFront(int value) {
        if (isFull()) {
            return false;
        }
        front = (front - 1 + capacity) % capacity;
        arr[front] = value;
        return true;
    }

从front删除对应front++,那么从front插入就是对应(front - 1 + capacity) % capacity对应的位置

    public boolean insertLast(int value) {
        if (isFull()) {
            return false;
        }
        arr[rear] = value;
        rear = (rear + 1) % capacity;
        return true;
    }

要注意从rear插入是先赋值再修改rear指针(++),而front插入则是先修改指针(–)再赋值,这是由两个指针的移动特点决定的。

    public boolean deleteFront() {
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % capacity;
        return true;
    }

    public boolean deleteLast() {
        if (isEmpty()) {
            return false;
        }
        rear = (rear - 1 + capacity) % capacity;
        return true;
    }

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

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


版权声明:本文为qq_44793993原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。