【隊列】隊列的分類和實現


隊列簡介

隊列也是一種線性結構。但它只能在表的一端追加元素(這端叫做隊尾),另一端刪除元素(這端叫做隊頭) 。因此隊列是一種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()】,那么就是當做一個棧使用。
 
雙端隊列可以用雙向鏈表實現。
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM