四、佇列的概念和實現

臣有一事不知當不當講發表於2018-04-09

佇列是一種特殊的線性表。

佇列僅能線上性表的兩端進行操作:

隊頭(Front):取出資料元素的一端;

隊尾(Rear):插入資料元素的一端。

佇列的特性——先進先出(First In First Out)

佇列的操作:建立、銷燬、清空(clear())、進佇列(add())、出佇列(remove())、獲取隊頭元素(front())、獲取隊尾元素(length())。

父類佇列的實現:

template <typename T>
class Queue : public Object
{
public:
    virtual void add(const T& e) = 0;
    virtual void remove() = 0;
    virtual T front() const = 0;
    virtual void clear() = 0;
    virtual int length() const = 0;

};

一、佇列的順序實現(StaticQueue)

類别範本:使用原生陣列作為佇列的儲存空間。

使用模板引數(N)決定佇列的最大容量。

StaticQueue實現要點(迴圈計數法):

關鍵操作:


具體實現如下:

template <typename T, int N>  //使用模板引數決定佇列的最大容量
class StaticQueue : public Queue<T>
{
protected:
    T m_space[N];  //佇列儲存空間,N為模板引數
    int m_front;  //隊頂標識
    int m_rear;   //隊尾標識
    int m_length;  //當前佇列的大小
 public:
    StaticQueue()
    {
        m_rear = 0;
        m_length = 0;
        m_front = 0;
    }

    int capacity() const
    {
        return N;
    }

    /***********迴圈計數法***********/

    void add(const T& e)  //O(1)
    {
       if( m_length < N)
       {
        m_space[m_rear] = e;
        m_rear = (m_rear + 1) % N; //迴圈計數
        m_length++;
       }
       else
       {
           THROW_EXCEPTION(InvalidOperationException,"No space in current queue...");
       }
    }

    void remove()//O(1)
    {
        if( 0 < m_length)
        {
           m_front = (m_front + 1) % N;   //變換隊頭
           m_length--;
        }
        else
        {
           THROW_EXCEPTION(InvalidOperationException,"No element in current queue...");
        }
    }

    T front() const//O(1)
    {
        if( m_length > 0)
        {
            return m_space[m_front];
        }
        else
        {
            THROW_EXCEPTION(InvalidOperationException,"No element in current queue...");
        }
    }

    void clear()//O(1)
    {
        m_front = 0;
        m_rear = 0;
        m_length = 0;
    }

    int length() const//O(1)
    {
        cout<< m_front - m_rear<<endl;

        cout<< "printf again!" << endl;

        cout<< m_length <<endl;

        return m_length;
    }

};

執行測試程式碼

int main()
{
    StaticQueue<int, 5> sq;

    for(int i=0; i<5; i++)
    {
        sq.add(i);
    }

    while (sq.length() > 0)
    {
        cout << sq.front() << endl;

        sq.remove();
    }

    return 0;
}

輸出:01234。

小結:佇列是一種特殊的線性表,具有先進先出的特性;

佇列只允許線上性表的兩端進行操作,一端進,一端出;

StaticQueue使用原生陣列作為內部儲存空間;

StaticQueue的最大容量由模板引數決定;

StaticQueue採用迴圈計數法來提高佇列操作效率。


二、鏈式佇列(LinkQueue)的實現

資料元素為類型別,StaticQueen的物件在建立時,會多次呼叫元素型別的建構函式,影響效率。

設計要點:類别範本,抽象父類Queue的直接子類;

                 在內部使用鏈式結構實現元素的儲存;

                 只在連結串列的頭部和尾部進行操作。

在鏈式棧的實現中,可以使用單連結串列來實現,但是因為涉及到遍歷操作(insert()),所以效率上並不是很高。


佇列鏈式儲存實現的優化:


通過Linux核心連結串列來實現鏈式佇列:


template <typename T>
class LinkQueue : public Queue<T>
{
protected:
    //LinkList<T> m_list;

    struct Node : public Object  //帶頭結點 使用Linux核心連結串列
    {
      list_head head;
      T value;
    };

    list_head m_header;
    int m_length;

public:
    LinkQueue()
    {
        m_length = 0;

        INIT_LIST_HEAD(&m_header);  //初始化
    }

    void add(const T& e)
    {
        //m_list.insert(e);   //O(n)  與佇列的元素個數成正比

        Node* node = new Node();

        if( node != NULL)
        {
            node->value = e;

            list_add_tail(&node->head, &m_header);  //O(1)

            m_length++;
        }
        else
        {
            THROW_EXCEPTION(InvalidOperationException,"No memory to add new element...");
        }
    }

    void remove()  //O(1)
    {
        if(m_length > 0)
        {
            //m_list.remove(0);

            list_head* toDel = m_header.next; //頭結點指向的位置

            list_del(toDel); //Linux核心連結串列的函式

            m_length--;

            delete list_entry(toDel, Node, head);
        }
        else
        {
            THROW_EXCEPTION(InvalidOperationException,"No element in current queue...");
        }
    }

    T front() const //O(1)
    {
        if( m_length > 0)
        {
            return list_entry(m_header.next, Node, head)->value;
        }
        else
        {
           THROW_EXCEPTION(InvalidOperationException,"No element in current queue...");
        }
    }

    void clear() //O(n)
    {
        while( m_length > 0)
        {
            remove();
        }
    }

    int length() const//O(1)
    {
        return m_length;
    }

    ~LinkQueue()
    {
      clear();
    }
};

測試程式碼如下,在主函式中實現一個Test類,使用鏈式佇列來儲存這個類作為佇列成員。

class Test : public Object
{
public:
    Test()
    {
      cout << "Test()" <<endl;
    }

    ~Test()
    {
        cout << "~Test()" <<endl;
    }
};


int main()
{
    LinkQueue<int> lq;
    LinkQueue<Test> lq1;
    StaticQueue<Test, 10> lq2;

    for(int i=0; i<5; i++)
    {
        lq.add(i);
    }

    while (lq.length() > 0)
    {
        cout << lq.front() << endl;

        lq.remove();
    }

    return 0;
}
通過列印結果可以看出,順序佇列在實現自定義類型別資料元素儲存時,需要將每個物件的建構函式和解構函式都呼叫(原因參見《二、單連結串列的實現和遍歷,迴圈連結串列、雙向迴圈連結串列的實現》中的第二節——單連結串列的具體實現),效率不高。鏈式佇列則沒有這種效率上的缺點,直接將資料儲存在佇列中。


小結:StaticQueue在初始化的時候可能多次呼叫元素型別的建構函式;

LinkList的組合使用,可以實現鏈式佇列的功能,但是效率不高;

LinkQueue的最終實現使用了Linux核心連結串列;

LinkQueue中入隊和出隊的操作可以在常量時間內完成。



相關文章