数据结构与算法之美(笔记4)队列

如何理解队列?

先进者先出,这就是典型的“队列”。

另外,同栈类似,队列也支持两个操作,入队和出队。

如何实现顺序队列?

我们可以使用数组来实现队列,也可以使用链表来实现队列。对于队列的数组实现,与栈不同,栈只需要一个栈顶指针,队列需要两个指针,一个头指针,一个尾指针。

当有数据入队的时候,tail指针就向后移动一位,当有数据出队的时候head指针就向后移动一位。随着不断地入队和出队,你肯定会发现,当tail移动到末尾的时候,即使前面的数组仍然有空间,但这个时候也无法往里面添加数据了。这个时候,我们可以想到用数组的数据搬移。但如果每一次有数据入队就使用数据搬移,那明显时间复杂度就是O(n)。

实际上,我们在出队的时候,如果没有空闲的空间了,我们只需要在入队的时候,再集中触发一次数据搬移的操作。这样的改进出队的时间间复杂度为O(1),入队的平均时间复杂度为:head指针出现在0,1,2....n,的概率都是1/n+1,而每一种情况的时间复杂度是1,n,n-1,.....1,累加起来的平均时间复杂度应该是O(n)。

这里给出顺序队列的代码实现:

class arrayQueue{
private:
    int* arr;
    int capacity;
    int head;
    int tail;
public:
    arrayQueue(int size){
        arr = new int[size];
        capacity = size;
        head = 0;
        tail = 0;
    }

    void enqueue(int elem){
        if(tail == capacity-1){
            if(head == 0){
                return;// 队满
            }else{
                for(int i=head;i<tail;++i){// 数据搬移
                    arr[i-head] = arr[i];
                }
                tail-=head;
                head = 0;
            }
        }
        arr[tail++] = elem;
    }

    int dequeue(){
        if(head == tail){
            return -1;
        }
        int temp = arr[head++];
        return temp;
    }

    void  print(){
        for(int i=head;i<tail;++i){
            cout << arr[i] << endl;
        }
    }
};

如何实现链式队列?

同数组队列类似,我们同样需要两个指针,head指针和tail指针,他们分别指向链表的第一个结和最后一个结点。

这里直接给出代码的实现:

class listQueue{
private:
    Node* head;
    Node* tail;
public:
    listQueue(){
        head = new Node;
        tail = head;
    }
    void enqueue(int elem){
        Node* newNode = new Node;
        newNode->data = elem;
        newNode->next = NULL;

        tail->next = newNode;
        tail = tail->next;
    }
    void dequeue(){
        if(head == tail){
            return;
        }
        head->next = head->next->next;
    }

    void print(){
        for(Node* p=head->next;p!=tail;p=p->next){
            cout << p->data << endl;
        }
        cout << tail->data << endl;
    }
};

如何实现循环队列?

我们刚刚使用数组来实现队列的时候,在tail == capacity的时候,会发生数据的迁移,这样入队的性能就受到的影响。

循环队列,顾名思义,是一个环,我们把首尾进行相连,形成了一个环。

通过这样的方法我们避免了数据的搬移,但是如何确定队空和队满的条件?

队列为空的判断条件仍然是 head == tail。但队列满的判断条件是(tail + 1)%n = head,实际上,当队满时,图中的tail指向的位置实际上是没有存储数据的,所以,循环队列会浪费一个数组的存储空间。那么访问呢?这里也要注意了,数组是支持随机访问的,但是对于循环队列来说,我们进行访问的时候,比如你想查询最后一个结点的时候,应该这样:arr[tail%n],注意要与循环队列的大小进行取模。

这里给出代码的实现:

class loopQueue{
private:
    int* arr;
    int capacity;
    int head;
    int tail;
public:
    loopQueue(int size){
        arr = new int[size];
        capacity = size;
        head = 0;
        tail = 0;
    }
    void enqueue(int elem){
        if((tail+1)%capacity == head){
            return;
        }
        arr[tail] = elem;
        tail = (tail+1)%capacity;
    }
    int dequeue(){
        if(tail == head){
            return -1;
        }
        int temp = arr[head];
        head = (head+1)%capacity;
        return temp;
    }
    void print(){
        for(int i=head;i!=tail;i=(i+1)%capacity){
            cout << arr[i] << endl;
        }
    }
};

阻塞队列

阻塞队列其实就是在队列基础上增加了阻塞操作。简单来说,就是在队列为空的时候,从队头取数据会被阻塞。因为这个时候没有数据可以取,直到队列队列中有数据才可以返回。如果队列的已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲的位置后再插入数据,然后再返回。

其实这就是一个生产者与消费者的模型,可以有效第协调生产和消费的速度。当生产者产数据的速度过快,“消费者”来不及消费,存储数据的队列很快就会被满了。这个时候,生产者就阻塞等待,直到消费者消费了数据,生产者 才会被唤醒继续生产。

举个实际的例子:在计算机视觉中,我们经常要用摄像头读取图片,读取图片的速度一般来说能够达到帧率。但是如果这个时候,我们处理图片的速度比较快,那么我们就得一直等待图的产生才行。如果我们把读取图片比作生产者,处理图片比作消费者,那么我们用阻塞队列多线程的话,当队列中没有满的时候,图片可以一直读取,这样就节省了处理图片的时间。

队列在线程池等有限资源池的应用

当我们向一个固定大小的线程池中请求一个线程的时候,如果线程池中没有空闲的资源了,这个时候线程池如何处理这个请求?是拒绝请求还是排队请求?各种处理策略又是如何实现的呢?

我们有两种处理的策略。第一种是非阻塞型的处理方式,直接拒绝任务请求;另一种是阻塞型的处理方式,将请求排队,等到有空闲的线程时,取出排队的请求继续处理。那么如何储存排队的请求?

我们希望用一个先进先服务的策略,所以队列就很适合了。这里有两种实现队列的方式,一种是数组,一种是链表。对于链表来说,我们可以实现一个无限长的队列,但是可能会导致过多的请求排队,请求处理的响应时间太长。所以,针对响应时间比较敏感的系统,基于链表实现的队列是不合适的。

而对于数组来说,我们队列的大小有限,所以线程中排队的请求超过队列大小时,接下来的请求就会被拒绝,这种实现方式,响应时间不会很长,但是你需要事先设置一个合理的队列大小。

另外,出啦线程池以外,对于大部分资源有限的场景,当没有空闲资源的时候,基本上都可以通过”队列“这种数据结构事先请求排队。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值