数据结构十——队列

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

1 队列

队列:可以想象成排队买票,先来的人先买,后到的人站在队尾。先进者先出,这就是队列。

队列的操作:入队(enqueue)、出队(dequeue)。入队:在队列尾部添加一个元素。出队:在队列头部移除一个元素。

2 队列的实现

队列的实现可以用数组,也可以用链表,分别称为顺序队列、链式队列。

2.1 基于数组的实现

public class ArrayQueue {
    private String[] items;
    private int n = 0;
    private int head;
    private int tail;
    public ArrayQueue(int capacity){
        this.n = capacity;
        items = new String[this.n];
        head = tail = 0;
    }

    /**
     * 在队尾插入一个元素
     * @param value
     * @return
     */
    public  boolean enqueue(String value){
        if(tail >=n) return false;//超出容量
        items[tail] = value;
        tail++;
        return true;
    }

    /**
     * 出队:从对头删除一个元素
     * @return
     */
    public String dequeue(){
        if(head==tail) return null;
        String ret = items[head];
        head++;
        return ret;
    }
}

举个 例子,画画图可以发现,随着入队、出队操作,head、tail指针不断向右移动,极端情况是head、tail都在右边,即使数组有空间,也不能再做入队操作。

如何解决这个问题呢?我们可以在不能入队的时候,将head、tail之间的元素,拷贝到[0,tail-head]之间。

	public  boolean enqueue(String value){
        if(tail==n){
            if(head==0) return false;
            for(int i=head;i<tail;i++){
                items[i-head] = items[i];
            }
            tail = tail-head;
            head = 0;
        }
        items[tail] = value;
        tail++;
        return true;
    }

在这个版本实现中,出队操作时间复杂度是O(1),入队时间复杂度是多少呢?直觉认为平均时间复杂度应该还是O(1)。无证明。

2.2 基于链表的实现

需要两个指针head、tail分别指向队列头部、队列尾部。入队的时候tail.next=new node,tail=tail.next;出队的时候,head=head.next。
代码

3 循环队列

3.1 数组实现

在2.1基于数组实现的队列中有数据迁移的操作。如果是一个循环队列就不需要数据迁移操作了。循环队列是指它长的像一个环,原本队列头部、和队列尾部形成一条直线。现在把队列头部和队列尾部扳成了一个环。

图中队列长度为8,head=4,tail=7。
当插入a的时候,items[7]=a,tail=(tail+1)%8=0。
当插入b的时候,items[0]=b,tail=(tail+1)%8=1。

通过这样的方式,就避免了数据搬移的工作。
循环队列的实现重点是判断什么时候为空,什么时候队列已满,不能再添加数据。
在一般队列中,当head=tail的时候,队列为空。循环队列也是。
在一般队列中,当tail=n的时候,队列已满,不能再添加数据。循环队列需要多举几个例子,画图总结规律。

图中head=4,tail=3,数组长度len=8。如果再插入数据tail=(tail+1)%8=head,tail=head,这与队列为空的条件重复了。所以,我们在(tail+1)%8=head的时候后,就不再插入数据。也就是说items[tail]是没有值的。
代码

3.2 基于链表实现

基于链表实现就更简单了,将tail.next=head即可。

4 并发队列和阻塞队列

阻塞队列就是在队列的基础上加了阻塞的操作。例如当队列为空的时候,在队列头部取数据的时候会进入阻塞状态,直到队列中有数据的时候才返回数据。当队列满了的时候,往队列中插入数据的时候会进入阻塞状态,直到队列中有空间存储数据的时候才入队成功,然后返回。
在这里插入图片描述
其实,这就是一个生产者-消费者模型。

这种生产者消费者模型可以有效协调生产速度和消费速度。例如当生产者生产速度过快,消费者来不及消费的情况下,生产者很快会填满队列,生产者进入阻塞状态,只有当消费者消费之后,有了剩余空间,生产者才能继续生产。

不仅如此,我们开可以分配1个生产者,多个消费者。
在这里插入图片描述

在多线程情况下就会存在线程安全问题。如何实现一个线程安全的队列呢?线程安全的队列被称为并发队列。用CAS,可以实现一个高效地安全队列。

5 队列应用在有限资源的场景下

队列主要应用在资源有限的场景下。例如数据库连接池里的连接是有限的,服务器每秒能处理的请求量是有限的。

当服务器的请求池中有空闲的服务线程的时候,我们希望公平地处理每一个到访的请求。这个时候我们将请求加入到队列中。当服务器的请求池中有空闲服务线程的时候,从队列取请求,进行服务。

队列的实现有两种方式:数组和链表。链表实现的队列可以无限扩容,可以接受无限的请求,但这容易引起内存溢出。数组实现的队列只能接受一定个数的请求,超出数量,直接返回无法访问。但是队列大小的设定需要权衡用户体验以及服务器的内存大小。

发布了148 篇原创文章 · 获赞 35 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/flying_all/article/details/100030078