• 【队列】队列的分类和实现


    队列简介

    队列也是一种线性结构。但它只能在表的一端追加元素(这端叫做队尾),另一端删除元素(这端叫做队头) 。因此队列是一种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
    ",queue.length());
        printf("is empty? :%d
    ",queue.isEmpty());
    
    
        puts("
    ----------put 10 elements to the queue-----------");
        for(int i=0;i<10;++i){
            queue.enQueue(i);
        }
        printf("length is :%d
    ",queue.length());
        printf("is empty? :%d
    ",queue.isEmpty());
    
        printf("--------pop 15 elements from the queue-------------
    ");
    
        for(int i=0,e;i<15;++i){
            queue.getFront(e);
            if(queue.deQueue())
                printf("%d ",e);
        }
        printf("
    length is :%d
    ",queue.length());
        printf("is empty? :%d
    ",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
    ",queue.length());
        printf("is empty? :%d
    ",queue.isEmpty());
     
        puts("
    ----------put 10 elements to the queue-----------");
        for(int i=0;i<10;++i){
            queue.enQueue(i);
        }
        printf("length is :%d
    ",queue.length());
        printf("is empty? :%d
    ",queue.isEmpty());
        
        printf("--------pop 5 elements from the queue-------------
    ");
        
        for(int i=0,e;i<5;++i){
            queue.getFront(e);
            if(queue.deQueue())
                printf("%d ",e);
        }
        printf("
    length is :%d
    ",queue.length());
        printf("is empty? :%d
    ",queue.isEmpty());
     
        puts("
    ----------put 2 elements to the queue-----------");
        
        for(int i=0,e;i<2;++i){
            queue.enQueue(i+100);
        }
     
        printf("
    length is :%d
    ",queue.length());
        printf("is empty? :%d
    ",queue.isEmpty());
    
        printf("--------pop all elements from the queue-------------
    ");
        while(!queue.isEmpty()    ){
            int e;
            queue.getFront(e);
            if(queue.deQueue())
                printf("%d ",e);
        }
        printf("
    length is :%d
    ",queue.length());
        printf("is empty? :%d
    ",queue.isEmpty());
     
        return 0;
    } 

    双端队列

    双端队列是一种两端都可以 删除元素和追加元素的线性结构。双端队列比普通的队列更加灵活。
     
    如果我们在使用时,自己限制自己的操作行为,则可以将双端队列当成其它的数据结构来使用。
     
    如果我们对一个双端队列只调用他的removeFirst() 和 addLast() ,那么就是当做一个队列使用。
    如果我们对一个双端队列只调用他的addLast() 和 removeLast() 【或者只调用addFirst和removeFirst()】,那么就是当做一个栈使用。
     
    双端队列可以用双向链表实现。
     
     
  • 相关阅读:
    Typora Writings
    Xcode7.3 beta 新功能
    最美应用API接口分析
    'Project Name' was compiled with optimization
    web前端开发与iOS终端开发的异同[转]
    2015-12-19_16_30_15
    Xcode搭建Python编译环境
    jsPach.qq.com
    Q&AApple’s Craig Federighi talks open source Swift, Objective-C and the next 20 years of development
    .NET Core项目与传统vs项目的细微不同
  • 原文地址:https://www.cnblogs.com/lulipro/p/7498459.html
Copyright © 2020-2023  润新知