队列队列的分类和实现

Posted lulipro - 代码钢琴家

tags:

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

队列简介

队列也是一种线性结构。但它只能在表的一端追加元素(这端叫做队尾),另一端删除元素(这端叫做队头) 。因此队列是一种FIFO (先进先出)特性的线性数据结构。
从队头删除元素的操作叫做出队,从队尾追加元素的操作叫做入队。

如图是含有n个元素的队列的模型。根据队列的出入元素特点,可以确定,元素a1最先入队,紧接着a2,s3 ... 如果a2要出队,必须等a1出队。a1最先入队,也是最先出队,an最后入队,也是最后出队。

 

链式队列

链式队列是队列的实现方式之一。链式队列内部使用带头结点的单向链表来实现。它的好处的是灵活,队列容量理论上是不受限制的。

我们使用链表的首结点来表示队列的队头,链表的尾结点代表队尾。

当队列为空时,队尾元素指针指向头结点headNode。

 

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
#define NDEBUG

struct Node{
    int element;     //结点保存的元素
    Node*next;       //next指针
    Node(int e=0, Node*nxt=NULL):element(e),next(nxt)
    {
    }
};

class LinkedQueue
{
private:
    Node headNode;       //头结点 ,headNode.next 就是队头结点的指针
    Node* pRearNode;     //表的最后一个结点的指针,队尾结点的指针
    int size;            //队列实际元素个数

public:
    LinkedQueue():headNode(),pRearNode(&headNode),size(0)
    {
        //初始化时, pRearNode 指向 headNode
    }
    ~LinkedQueue()
    {
        clear();
    }


    void clear()
    {
        Node*p = headNode.next;
        Node*t;
        while(p!=NULL){
            t = p;
            p=p->next;
            delete t;
        }
        //回归初始状态
        headNode.next = NULL;
        pRearNode = &headNode;
        size=0;

    }

    bool isEmpty()const
    {
        #ifndef NDEBUG
        if(size==0){     //调试用
            assert(pRearNode == &headNode);
        }
        #endif
        return (size==0 && pRearNode == &headNode ) ;
    }

    int length()const
    {
        return size;
    }

    //入队
    bool enQueue(int e)
    {
        Node*p_new = new Node(e,NULL);
        pRearNode->next = p_new;
        pRearNode = p_new;
        size++;
    }

    //出队
    bool deQueue()
    {
        if(isEmpty()) return false;   //如果队为空

        Node*p_del = headNode.next;              //获取待删结点的指针
        headNode.next = (headNode.next)->next;    //跳过,链接 
        //如果删除的是最后一个结点。则应该重新赋值pRearNode,指向headNode
        if(pRearNode == p_del) pRearNode = &headNode;
        delete p_del;
        size--;
    }

    //获取队尾元素
    bool getRear(int& e) const
    {
        if(!isEmpty()){
            e = pRearNode->element;
            return true;
        }   
        return false;
    }

    //获取队头元素
    bool getFront(int& e)const
    {
        if(!isEmpty()){
            e = (headNode.next)->element;               
            return true;
        }
        return false;
    }
};

int main()
{
    using namespace std;

    LinkedQueue queue;

    printf("length is :%d\\n",queue.length());
    printf("is empty? :%d\\n",queue.isEmpty());


    puts("\\n----------put 10 elements to the queue-----------");
    for(int i=0;i<10;++i){
        queue.enQueue(i);
    }
    printf("length is :%d\\n",queue.length());
    printf("is empty? :%d\\n",queue.isEmpty());

    printf("--------pop 15 elements from the queue-------------\\n");

    for(int i=0,e;i<15;++i){
        queue.getFront(e);
        if(queue.deQueue())
            printf("%d ",e);
    }
    printf("\\nlength is :%d\\n",queue.length());
    printf("is empty? :%d\\n",queue.isEmpty());

    return 0;
}

 

循环队列

也可以使用顺序结构(数组)来实现队列,将队头存放在数组开头的位置, 但是我们会遇到一个问题:如果执行出队后,array[0]就空出来了,但是又不能被利用,因为队列只能在队尾追加元素,这样就会造成“假满”的情况。
 
           
 
我们可以将一个固定长度的数组在逻辑上臆造成一个环形。这样,队头元素存放的位置不是固定的,他会随着 出队 动态改变,而队尾元素也会随着入队 动态改变。可以将front和rear变量看做是2个小孩子围着一颗树你追我赶一样,只要他们在追赶过程中没有相遇,队列就不是满的。 这样就会让每个数组空间都能得到利用。
 
 

具体实现要点

1、设一个变量front,保存队头元素在数组中的位置。设一个rear变量,保存下一个即将入队元素在数组中的位置。初始化时: front=rear = 0
2、设一个常量QUEUE_CAPACITY,保存队列的最大容量。
3、在出队后,front变为   front = (front + 1) %QUEUE_CAPACITY 。例如队列最多容量为5,某一时刻front的值为4,则 (4+1)%5 =0 ,front就又会变成0。
      在入队后,rear变为:    rear = (rear + 1) %QUEUE_CAPACITY

我们必须让循环队列的索引值限制在一定的范围内(长度我n的数组的索引一定是0~n-1),而不是让rear一直加1或者front一直减1。可以使用数学问题去解决:我们知道:将一个数M对n取模后,得到的结果将被映射到 0~n-1之间,循环队列就是利用的这个特点来完成索引的变化的。

 

实现方式1

设一个变量size,保存队列中元素的实际个数。每次出队,size减1,入队,size加1。而队列的 空,满,队列长度都需要使用他来实现。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>


class CycleQueue
{
private:
    enum{QUEUE_CAPACITY = 10};  //内部常量,存储循环队列的最大容量

    int*elements ;     //存储元素的数组
    int front;         //保存队头结点的索引
    int rear;          //保存下一个即将入队元素在数组中的索引。
    int size;          //保存队列的实际容量

public:
    CycleQueue()
    {
        elements = new int[QUEUE_CAPACITY];
        front = rear=0;
        size =0;
    } 
    ~CycleQueue()
    {
        delete[] elements;     
    }


    void clear()
    {
        //回归初始状态
        front = rear=0;
        size =0;

    }

    bool isEmpty()const
    {
        return size ==0;
    }
    bool isFull()const
    {
        return size == QUEUE_CAPACITY;
    }

    int length()const
    {
        return size;
    }

    //入队
    bool enQueue(int e)
    {
        if(isFull()) return false;

        elements[rear] = e;

        rear = (rear+1)%QUEUE_CAPACITY;
        size++;
        return true;
    }

    //出队
    bool deQueue()
    {
        if(isEmpty()) return false;

        front = (front+1)%QUEUE_CAPACITY;
        size --;
        return true;
    }

    //获取队尾元素
    bool getRear(int& e) const
    {
        if(isEmpty()) return false;

        e= elements[rear-1];
        return true;
    }

    //获取队头元素
    bool getFront(int& e)const
    {
        if(isEmpty()) return false;

        e= elements[front];
        return true;

    }
};

 

实现方式2

可以不使用size变量,另一种方法也可以实现队列的 空,满,队列长度的获取。即:让队列空出一个元素空间,我称他为标记空间。因此数组长度为n的循环队列,则只能存储n-1个元素。

这个标记空间是循环队列中队尾元素逻辑上的后一个数组空间,但是这个空间在数组中的实际位置也是随着出队,入队动态变化的。

ARRAY_CAPACITY是内部数组实际的容量,他的值是QUEUE_CAPACITY+1。因为队列容器比数组容量少一。

空判断:rear == front ? ”空“:"不为空"

满判断:(rear+1)%ARRAY_CAPACITY == front   ? ”满“:"不为满"

实际元素个数:(rear - front + ARRAY_CAPACITY) % ARRAY_CAPACITY

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>


class CycleQueue
{
private:
    enum{QUEUE_CAPACITY=10};     //储循环队列的最大容量.
    enum{ARRAY_CAPACITY=QUEUE_CAPACITY+1};   //数组容量 

    int*elements ;     //存储元素的数组 
    int front;         //保存队头结点的索引 
    int rear;          //保存下一个即将入队元素在数组中的索引。
    
public:
    CycleQueue()
    {
        elements = new int[ARRAY_CAPACITY];
        front = rear=0;
    }  
    ~CycleQueue()
    {
        delete[] elements;     
    }
    
    
    void clear()
    {
        //回归初始状态 
        front = rear=0;
    }
    
    bool isEmpty()const
    {
        return front == rear;
    }
    bool isFull()const 
    {
        return (rear+1)%ARRAY_CAPACITY == front;
    }
    
    int length()const 
    {
        return (rear-front + ARRAY_CAPACITY)%ARRAY_CAPACITY;
    }
    
    //入队 
    bool enQueue(int e)
    {
        if(isFull()) return false;
        
        elements[rear] = e;
        
        rear = (rear+1)%ARRAY_CAPACITY;
        return true;
    }
    
    //出队 
    bool deQueue()
    {
        if(isEmpty()) return false;
        
        front = (front+1)%ARRAY_CAPACITY;
        return true;
    }
    
    //获取队尾元素 
    bool getRear(int& e) const 
    {
        if(isEmpty()) return false;
        
        e= elements[rear-1];
        return true;
    }
    
    //获取队头元素 
    bool getFront(int& e)const 
    {
        if(isEmpty()) return false;
        
        e= elements[front];
        return true;
    }
};

int main()
{
    using namespace std;
    CycleQueue queue;
    
    printf("length is :%d\\n",queue.length());
    printf("is empty? :%d\\n",queue.isEmpty());
 
    puts("\\n----------put 10 elements to the queue-----------");
    for(int i=0;i<10;++i){
        queue.enQueue(i);
    }
    printf("length is :%d\\n",queue.length());
    printf("is empty? :%d\\n",queue.isEmpty());
    
    printf("--------pop 5 elements from the queue-------------\\n");
    
    for(int i=0,e;i<5;++i){
        queue.getFront(e);
        if(queue.deQueue())
            printf("%d ",e);
    }
    printf("\\nlength is :%d\\n",queue.length());
    printf("is empty? :%d\\n",queue.isEmpty());
 
    puts("\\n----------put 2 elements to the queue-----------");
    
    for(int i=0,e;i<2;++i){
        queue.enQueue(i+100);
    }
 
    printf("\\nlength is :%d\\n",queue.length());
    printf("is empty? :%d\\n",queue.isEmpty());

    printf("--------pop all elements from the queue-------------\\n");
    while(!queue.isEmpty()    ){
        int e;
        queue.getFront(e);
        if(queue.deQueue())
            printf("%d ",e);
    }
    printf("\\nlength is :%d\\n",queue.length());
    printf("is empty? :%d\\n",queue.isEmpty());
 
    return 0;
} 

双端队列

双端队列是一种两端都可以 删除元素和追加元素的线性结构。双端队列比普通的队列更加灵活。
 
如果我们在使用时,自己限制自己的操作行为,则可以将双端队列当成其它的数据结构来使用。
 
如果我们对一个双端队列只调用他的removeFirst() 和 addLast() ,那么就是当做一个队列使用。
如果我们对一个双端队列只调用他的addLast() 和 removeLast() 【或者只调用addFirst和removeFirst()】,那么就是当做一个栈使用。
 
双端队列可以用双向链表实现。
 
 

以上是关于队列队列的分类和实现的主要内容,如果未能解决你的问题,请参考以下文章

队列队列的分类和实现

数据结构与算法--队列

perl中的队列

Java实现队列结构的详细代码

线性表--08---优先队列

利用redis List队列简单实现秒杀 PHP代码实现