队列(二十三)

        我们在上节博客中学习了栈的相关知识,今天我们来学习下队列。那么什么是队列呢?队里是一种特殊的线性表,队列仅能在线性表的两端进行操作;队头(Front)是取出数据元素的一端,队尾(Rear)是插入数据元素的一端队列的特性是先进先出(First in first out),关系图如下所示

图片.png

        下来我们来看看队列的常用操作,如下

        1、创建队列(Queue())

        2、销毁队列(~Queue())

        3、清空队列(clear())

        4、进队列(add())

        5、出队列(remove())

        6、获取队头元素(front())

        7、获取队列的长度(length())


        下来我们来看看队列的结构框图,如下

图片.png

        那么队列的顺序实现如下图所示

图片.png

        下来我们来看看 StaticQueue 设计要点:首先必须是类模板,使用原生数组作为队列的存储空间;使用模板参数决定队列的最大容量

        StaticQueue 实现要点是循环计数法,关键操作1、进队列:m_space[m_rear] = e; m_rear = (m_rear + 1) % N;2、出队列:m_front = (m_front + 1) % N;

        队列的状态:1、队空:(m_length == 0) && (m_fronmt == m_rear); 2、队满:(m_length == N) && (m_front == m_rear)。

        下来我们来看看代码是怎样写的


Queue.h 源码

#ifndef QUEUE_H
#define QUEUE_H

#include "Object.h"

namespace DTLib
{

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;
};

}

#endif // QUEUE_H


StaticQueue.h 源码

#ifndef STATICQUEUE_H
#define STATICQUEUE_H

#include "Queue.h"
#include "Exception.h"

namespace DTLib
{

template < typename T, int N >
class StaticQueue : public Queue<T>
{
protected:
    T m_space[N];
    int m_front;
    int m_rear;
    int m_length;
public:
    StaticQueue()
    {
        m_front = 0;
        m_rear = 0;
        m_length = 0;
    }

    int capacity() const
    {
        return N;
    }

    void add(const T& e)
    {
        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()
    {
        if( m_length > 0 )
        {
            m_front = (m_front + 1) % N;
            m_length--;
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No element in current queue ...");
        }
    }

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

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

    int length() const
    {
        return m_length;
    }
};

}

#endif // STATICQUEUE_H

        我们来写个测试代码来看看代码是否正确

#include <iostream>
#include "StaticQueue.h"

using namespace std;
using namespace DTLib;

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

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

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

        queue.remove();
    }

    return 0;
}

        我们来看看结果

图片.png

        那么同样的参考我们上节实现的栈来说,目前实现的队列也存在一定的缺陷,那便是当数据元素为类类型时, StaticQueue 的对象在创建时会多次调用元素类型的构造函数,极大的影响了效率。所以我们需要实现链式队列来解决这个问题,实现的思路也很简单,参考前面的 StaticQueue 即可。下来我们来看看队列的链式存储实现结构图,如下所示

图片.png

        链式队列的设计要点如下:

        1、类模板,抽象父类 Queue 的直接子类;

        2、在内部使用链式结构实现元素的存储;

        3、只在链表的头部和尾部进行操作。


        结构图如下

图片.png
        下来我们来看看 LinkQueue 的源码是怎样写的


LinkQueue.h 源码

#ifndef LINKQUEUE_H
#define LINKQUEUE_H

#include "Queue.h"
#include "LinkList.h"
#include "Exception.h"

namespace DTLib
{

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

    }

    void add(const T& e)    // O(n)
    {
        m_list.insert(e);
    }

    void remove()       // O(1)
    {
        if( m_list.length() > 0 )
        {
            m_list.remove(0);
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No element in current queue ...");
        }
    }

    T front() const     // O(1)
    {
        if( m_list.length() > 0 )
        {
            return m_list.get(0);
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No element in current queue ...");
        }
    }

    void clear()    // O(n)
    {
        m_list.clear();
    }

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

};

}

#endif // LINKQUEUE_H

        我们来看看测试代码,如下

#include <iostream>
#include "LinkQueue.h"

using namespace std;
using namespace DTLib;

int main()
{
    LinkQueue<int> lq;

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

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

        lq.remove();
    }

    return 0;
}

        编译运行结果如下

图片.png

        我们已经实现了 LinkQueue 的代码,结果也是正确的。可是我们再看看它的效率是否很高呢?看看它们的时间复杂度,只有 add 和 clear 函数是 O(n) ,其他的都是 O(1)。因为在插入的时候我们必须遍历下整个链表,所以效率不是很高。再来对比下之前实现的 StaticQueue 的代码,它们的函数复杂度都是 O(1),那么是否有更好的解决办法吗?下来我们来看看队列链式存储实现的优化,如下图所示

图片.png

        结构图如下图所示

图片.png

        下来我们来看看基于 Linnx 内核链表的队列,源码如下


LinkQueue.h 源码

#ifndef LINKQUEUE_H
#define LINKQUEUE_H

#include "Queue.h"
#include "LinuxList.h"
#include "Exception.h"

namespace DTLib
{

template < typename T >
class LinkQueue : public Queue<T>
{
protected:
    struct Node : public Object
    {
        list_head head;
        T value;
    };

    list_head m_header;
    int m_length;
public:
    LinkQueue()     // O(1)
    {
        m_length = 0;

        INIT_LIST_HEAD(&m_header);
    }

    void add(const T& e)    // O(1)
    {
        Node* node = new Node();

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

            list_add_tail(&node->head, &m_header);

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

    void remove()       // O(1)
    {
        if( m_length > 0 )
        {
            list_head* toDel = m_header.next;

            list_del(toDel);

            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()    // O(n)
    {
        clear();
    }
};

}

#endif // LINKQUEUE_H

        我们看看在上面的 add 函数中调用的是 Linux 内核中的 list_add_tail 函数,我们来看看它的时间复杂度为多少,它的源码如下

图片.png

        我们再来看看 __list_add 函数是怎样实现的,如下

图片.png

        它的时间复杂度为 O(1),因而我们上面实现的 add 函数时间复杂度为 O(1)。比起之前实现的代码效率更高,我们来测试下此代码

#include <iostream>
#include "LinkQueue.h"

using namespace std;
using namespace DTLib;

int main()
{
    LinkQueue<Test> lq;

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

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

        lq.remove();
    }

    return 0;
}

        结果如下

图片.png

        我们再来测试下当数据元素为类类型时,看看结果,测试代码如下

#include <iostream>
#include "LinkQueue.h"
#include "StaticQueue.h"

using namespace std;
using namespace DTLib;

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

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

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

    for(int i=0; i<10; i++)
    {

    }

    while( lq.length() > 0 )
    {
        lq.remove();
    }

    return 0;
}

        我们看看结果怎样

图片.png

        在 LinkQueue 的创建中没有 Test 类对象的生成,但是在 StaticQueue 中有类对象的生成。通过今天对队列的学习,总结如下:1、StaticQueue 在初始化时可能多次调用元素类型的构造函数;2、LinkList 的组合使用能够实现队列的功能,但是不够高效;3、LinkQueue 的最终实现组合使用了 Linux 内核链表;4、LinkQueue 中入队和出队操作可以在常量时间内完成。

猜你喜欢

转载自blog.51cto.com/12810168/2178925