【Java】队列

1 队列

队列(queue)是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列尾部,而位于队列头部的人逐个离开

在队列中只能在队尾插入,在队首删除

方法名描述时间复杂度
push()
向队尾插入元素
O(1)

pop()

队首元素出队O(1)
peek()访问队首元素O(1)


1.1 实现

1.1 使用带哨兵的单向循环链表实现

类属性和结点类(使用泛型)

public class LinkedListQueue<E> {
    Node<E> head = new Node<>(null, null);
    Node<E> tail = head;
    private int size = 0; // 当前结点数
    private int capacity = Integer.MAX_VALUE; // 总容量

    static class Node<E> {
        E value;
        Node<E> next;

        public Node(E value, Node<E> next) {
            this.value = value;
            this.next = next;
        }
    }

    // 无参构造
    public LinkedListQueue() {
        tail.next = head;
    }
    
    // 有参构造
    public LinkedListQueue(int capacity) {
        this.capacity = capacity;
        tail.next = head;
    }

}

push()方法

// 向队列尾插入值,插入成功返回true,失败返回false
    public boolean push(E value) {
        if (isFull()) {
            return false;
        }
        Node<E> added = new Node<>(value, head);
        tail.next = added; // 此时新加入的元素就是队尾,把tail指向它
        tail = added;
        size++;
        return true;
    }

poll()方法

// 从队列头获取值,移除
    public E poll() {
        if (isEmpty()) {
            return null;
        }
        Node<E> first = head.next;
        head.next = first.next;
        if (first == tail) {
            tail = head; // 队内只有一个元素时,需要特殊判断
        }
        size--;
        return first.value;
    }

peek()方法

// 从队列头获取值,不移除
    public E peek() {
        if (isEmpty()) {
            return null;
        }
        return head.next.value;
    }

类内私有的方法

private boolean isEmpty() {
        return head == tail;
    }

    private boolean isFull() {
        return size == capacity;
    }

迭代器

public Iterator<E> iterator() {
        return new Iterator<E>() {
            Node<E> p = head.next;// 越过哨兵迭代

            @Override
            public boolean hasNext() {
                return p != head;
            }

            @Override
            public E next() {
                E value = p.value;
                p = p.next;
                return value;
            }
        };
    }

测试

public static void main(String[] args) {
        LinkedListQueue<Integer> queue = new LinkedListQueue<>();
        System.out.println("第一次入队:" + "3");
        queue.push(3);
        System.out.println("第二次入队:" + "8");
        queue.push(8);
        System.out.println("第三次入队:" + "6");
        queue.push(6);
        System.out.println();
        System.out.println("第一次出队:" + queue.poll());
        System.out.println("第二次出队:" + queue.poll());
        System.out.println("第三次出队:" + queue.poll());
    }


1.2 使用循环数组实现

使用循环数组的好处:

1.对比普通数组,起点和终点更为自由,不用考虑数据移动

2.“环“意味着不会存在越界问题

3.数组性能更佳

4.循环数组比较适合实现有界队列、RingBuffer等

类属性

注意 array 数组实际大小要比给定的 capacity 多一个单位(以后用来装尾指针)

public class ArrayQueue<E> {
    private E[] array;
    private int head = 0; // 头指针和尾指针初始都是0
    private int tail = 0;

    public ArrayQueue(int capacity) {
        array = (E[]) new Object[capacity + 1];
    }
}

push()方法

为了 tail 不超过数组索引,需要限制长度,加1的时候要 mod 数组长度

public boolean push(E value) {
        if (isFull()) {
            return false;
        }
        array[tail] = value;
        tail = (tail + 1) % array.length;
        return true;
    }

poll()方法

public E poll() {
        if (isEmpty()) {
            return null;
        }
        E value = array[head];
        head = (head + 1) % array.length;
        return value;
    }

peek()方法

public E peek() {
        if (isEmpty()) {
            return null;
        }
        return array[head];
    }

类内方法(判断队列是否空,是否满的条件)

public boolean isEmpty() {
        return head == tail;
    }

    public boolean isFull() {
        return (tail + 1) % array.length == head;
    }

迭代器

public Iterator<E> iterator() {
        return new Iterator<E>() {
            int p = head;

            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public E next() {
                E value = array[p];
                p = (p + 1) % array.length;
                return value;
            }
        };
    }

测试:同链表


2 双端队列

双端队列(deque--double ended queue)两端都可以进行操作(删除和添加)

2.1 使用带哨兵的双向循环链表实现

类属性和结点类

public class LinkedListDeque<E> {
    int size = 0;
    int capacity;
    Node<E> sentinel = new Node<>(null, null, null);

    static class Node<E> {
        Node<E> prev;
        E value;
        Node<E> next;

        public Node(Node<E> prev, E value, Node<E> next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }
    public LinkedListDeque(int capacity) {
        
        this.capacity = capacity;
        sentinel.next = sentinel;
        sentinel.prev = sentinel;
    }
}

push()方法

分为两种,队首和队尾

public boolean pushFirst(E value) {
        if (isFull()) {
            return false;
        }
        Node<E> a = sentinel;
        Node<E> b = sentinel.next;
        Node<E> added = new Node<>(a, value, b);

        a.next = added;
        b.prev = added;

        size++;
        return true;
    }

    public boolean pushLast(E value) {
        if (isFull()) {
            return false;
        }
        Node<E> a = sentinel.prev;
        Node<E> b = sentinel;
        Node<E> added = new Node<>(a, value, b);

        a.next = added;
        b.prev = added;

        size++;
        return true;
    }

注意双向链表每个结点都有两个指针,插入和删除的时候需要同时动三个结点:

a  added  b

pop()方法

public E popFirst() {
        if (isEmpty()) {
            return null;
        }
        Node<E> a = sentinel;
        Node<E> removed = sentinel.next;
        Node<E> b = removed.next;

        a.next = b;
        b.prev = a;
        size--;
        return removed.value;
    }

    public E popLast() {
        if (isEmpty()) {
            return null;
        }
        Node<E> b = sentinel;
        Node<E> removed = sentinel.prev;
        Node<E> a = removed.prev;

        a.next = b;
        b.prev = a;
        size--;
        return removed.value;
    }

peek()方法

public E peekFirst() {
        return sentinel.next.value;
    }

    public E peekLast() {
        return sentinel.prev.value;
    }

类内方法

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

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

迭代器

public Iterator<E> iterator() {
        return new Iterator<>() {
            Node<E> p = sentinel.next;

            @Override
            public boolean hasNext() {
                return p != sentinel;
            }

            @Override
            public E next() {
                E value = p.value;
                p = p.next;
                return value;
            }
        };
    }

测试

public static void main(String[] args) {
        LinkedListDeque<Integer> deque = new LinkedListDeque<>(10);
        System.out.println("第一次前端入队:8"); // 8
        deque.pushFirst(8);
        System.out.println("第二次前端入队:5"); // 5 8
        deque.pushFirst(5);
        System.out.println("第三次后端入队:10"); // 5 8 10
        deque.pushLast(10);

        System.out.print("此时的双端队列:");

        Iterator<Integer> it = deque.iterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");
        }

        System.out.println("\n\n第一次后端出队:" + deque.popLast());

        System.out.print("此时的双端队列:");
        it = deque.iterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");
        }

        System.out.println("\n\n第二次前端出队:" + deque.popFirst());

        System.out.print("此时的双端队列:");
        it = deque.iterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");
        }

        System.out.println("\n\n第三次前端出队:" + deque.popFirst());

        System.out.print("此时的双端队列:");
        it = deque.iterator();
        while (it.hasNext()) {
            System.out.print(it.next() + " ");
        }

    }


2.2 使用循环数组实现

和 1.2 使用的数组是一样的循环数组,不同的是实现的队列

队列只能一头插入一头删除,所以 1.2 中 head 和 tail 指针不停加1再取模往后移动就可以

而双端队列两头都可以操作,所以删除元素时,head 减1到队尾;插入元素时 tail 加1到后面

类属性

public class ArrayDeque<E> {
    E[] array;
    int head;
    int tail;
    int capacity;

    public ArrayDeque(int capacity) {
        array = (E[]) new Object[capacity + 1];
        this.capacity = capacity;
        this.head = 0;
        this.tail = 0;
    }
}

push()方法

public boolean pushFirst(E value) {
        if (isFull()) {
            return false;
        }
        head = dec(head, array.length);
        array[head] = value;
        return true;
    }

    public boolean pushLast(E value) {
        if (isFull()) {
            return false;
        }
        array[tail] = value;
        tail=inc(tail, array.length);
        return true;
    }

pop()方法

public E popFirst(){
        if (isEmpty()){
            return null ;
        }
        E value = array[head];
        head = inc(head, array.length);
        return value;
    }

    public E popLast(){
        if (isEmpty()){
            return null ;
        }
        tail =dec(tail, array.length);
        return array[tail];
    }

类内方法

public boolean isEmpty() {
        return head == tail;
    }

    public boolean isFull() {
        if (tail > head) {
            return tail - head == array.length - 1;
        } else if (tail < head) {
            return head - tail == 1;
        } else {
            return false;
        }
    }

    public int inc(int i, int length) {
        if (i + 1 > length) {
            return 0;
        }
        return i + 1;
    }

    public int dec(int i, int length) {
        if (i - 1 < 0) {
            return length - 1;
        }
        return i - 1;
    }

为了头尾指针不超过数组索引范围,所以自定义头尾指针增加和减少的方式

inc(increment 增加)

dec(decrement 减少)


3 优先队列

详情观看大顶堆

 使用大顶堆实现:

类属性:

public class PriorityQueue {
    int[] array = new int[10];
    int size = 0;
}

offer()方法

public boolean offer(int value) {
        if (isFull()) {
            return false;
        }
        array[size] = value;
        shiftUp(size);
        size++;
        return true;
    }

poll()方法

public int poll() {
        if (isEmpty()) {
            throw new NullPointerException();
        }
        int result = array[0];
        array[0] = array[size - 1];
        shiftDown(0);
        size--;
        return result;
    }

peek()方法

public int peek(){
        if (isEmpty()){
            throw new NullPointerException();
        }
        return array[0];
    }

shiftUp()上浮方法

private void shiftUp(int index) {
        int child = index;
        int parent = (index - 1) / 2;
        while (child > 0) {
            if (array[parent] < array[child]) {
                swap(parent, child);
            } else {
                break;
            }
            child = parent;
            parent = (child - 1) / 2;
        }
    }

 用孩子节点去和父节点比较,孩子节点大就上浮

shiftDown()下潜方法

private void shiftDown(int index) {
        int parent = index;
        int leftChild = 2 * parent + 1;
        int rightChild = leftChild + 1;
        int max = parent;

        if (leftChild < size && array[max] < array[leftChild]) {
            max = leftChild;
        }
        if (rightChild < size && array[max] < array[rightChild]) {
            max = rightChild;
        }
        if (max != parent) {
            swap(max, parent);
            shiftDown(max);
        }
    }

左右孩子如果比父节点大,就和父节点交换,然后父节点(比 max 小)下潜

工具方法

private void swap(int i, int j) {
        // 注意传递的是索引,可以间接交换数组两个值
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

private boolean isFull() {
        return size == array.length;
    }

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

测试

public static void main(String[] args) {
        PriorityQueue priorityQueue = new PriorityQueue();
        priorityQueue.offer(9);
        priorityQueue.offer(65);
        priorityQueue.offer(1);
        priorityQueue.offer(754);

        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
    }


4 阻塞队列

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值