1 前言
队列也是一种特殊的线性表,队列仅能在线性表的两端进行操作,队头(Front)为取出数据元素的一端,队尾为插入数据元素的一端,队列的特性为先进先出(First In First Out)
队列的操作包括创建队列,销毁队列,清空队列,进队列,出队列,获取队头元素,获取队列的长度,队列可以包括静态队列和动态队列,我们可以抽象出一个队列父类,静态队列和动态队列都继承于该类,队列的顺序实现如下图所示,我们需要一片连续的存储空间,需要两个标识变量,一个变量指向队头front,一个变量指向队尾rear(队尾指向的地方没有元素)
2 StaticQueue
设计要点
1.使用原生数组作为队列的存储空间
2.使用模板参数决定队列的最大容量
3.我们要设计成循环队列(循环队列才高效)
4.队空:(m_length == 0) &&(m_frontm_rear)
5.队满:(m_length == N) &&(m_frontm_rear)
抽象父类 Queue.h
#ifndef QUEUE_H
#define QUEUE_H
#include "object.h"
namespace CGSLib
{
template <typename T>
class Queue : public Object
{
virtual void remove() = 0;
virtual void clear() = 0;
virtual int length()const = 0;
virtual void add(const T& e) = 0;
virtual T front()const = 0;
};
}
#endif // QUEUE_H
StaicQueue代码如下
#ifndef STATICQUEUE_H
#define STATICQUEUE_H
#include "Queue.h"
#include "Exception.h"
namespace CGSLib
{
template <typename T,int N>
class StaticQueue : public Queue<T>
{
protected:
T m_space[N];
T m_front;
T m_rear;
int m_length;
public:
/*队列初始化函数*/
StaticQueue()
{
m_front = 0;
m_rear = 0;
m_length = 0;
}
/*队列删除函数,从队头删除*/
void remove()
{
if(m_length>0)
{
m_front = (m_front+1)%N;
m_length--;
}
else
{
THROW_EXCEPTION(InvalidOperationException,"no element to remove");
}
}
/*队列加入函数,从队尾加入,队尾本身指向一个空位*/
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 memery to add");
}
}
/*获取队头元素的函数*/
T front()const
{
if(m_length>0)
{
return m_space[m_front];
}
else
{
THROW_EXCEPTION(InvalidOperationException,"no element to get");
}
}
/*获取队列长度的函数*/
int length()const
{
return m_length;
}
/*获取队列容量的函数*/
int capacity()const
{
return N;
}
/*队列清除函数*/
void clear()
{
m_front = 0;
m_rear = 0;
m_length = 0;
}
};
}
#endif // STATICQUEUE_H
测试代码如下
using namespace std;
using namespace CGSLib;
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();
}
}
测试结果,结果无误
基于之前我们那篇栈的文章提到过静态栈的缺陷,类似的,静态队列也存在同样的缺陷,当数据元素为类类型时,静态队列的对象在创建时,会多次调用元素类型的构造函数,影响效率,所以我们下面就开始打造一个链式队列来提高效率
3 LinkQueue
链式队列本质上还是使用了链表的结构,但这里与链式栈不同的是,我们链式结构来实现队列的时候需要用到两个指针**,front指针**指向队列的首元素,rear指针指向队列的尾元素
链式队列的设计要点
1.类模板,抽象父类Queue的直接子类
2.在内部使用链表结构实现元素的存储
3.只能在链表的头部和尾部进行操作
LinkQueue代码
#ifndef LINKQUEUE_H
#define LINKQUEUE_H
#include "LinkList.h"
#include "Queue.h"
#include "Exception.h"
namespace CGSLib
{
template <typename T>
class LinkQueue : public Queue<T>
{
LinkList<T> m_queue;
public:
/*链式队列的删除元素,从链式队列的首结点删除也就是队头删除*/
void remove()//O(1)
{
if(m_queue.length()>0)
{
m_queue.remove(0);
}
else
{
THROW_EXCEPTION(InvalidOperationException,"no element to remove");
}
}
/*链式队列的删除函数*/
void clear()//O(n)
{
m_queue.clear();
}
/*链式队列的获取元素数目函数*/
int length()const//O(1)
{
return m_queue.length();
}
/*链式队列的添加元素函数,也就是从队尾添加*/
void add(const T& e)//O(n)
{
m_queue.instert(e);
}
/*获取链式队列队头元素*/
T front()const//O(1)
{
if(m_queue.length()>0)
{
return m_queue.get(0);
}
else
{
THROW_EXCEPTION(InvalidOperationException,"no element to get");
}
}
};
}
#endif // LINKQUEUE_H
测试函数:
#include <iostream>
#include "LinkQueue.h"
using namespace std;
using namespace CGSLib;
int main()
{
LinkQueue<int> queue;
for(int i=0;i<5;i++)
{
queue.add(i);
}
while(queue.length()>0)
{
cout<<queue.front()<<endl;
queue.remove();
}
}
测试结果:
0
1
2
3
4
可以看出我们的链式队列就成功了,但是对于链表队列的时间复杂度还是不理想,顺序队列的操作函数都是O(1)而我们的链式队列出现了两次O(n),造成低效率的原因是因为我们往链式队列中插入一个元素时需要从队头遍历到队尾才能插入,这是问题根源的所在,我们这里使用带头结点双向循环链表来做链式队列,而linux内核链表就是带头结点的双向循环链表,我们以它为基础来打造我们的链式队列
基于Linux链表的队列代码如下
#ifndef LINKQUEUE_H
#define LINKQUEUE_H
#include "LinuxList.h"
#include "Queue.h"
#include "Exception.h"
namespace CGSLib
{
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()
{
m_length = 0;
INIT_LIST_HEAD(&m_header);
}
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 to remove");
}
}
void clear()//O(n)
{
while(m_length>0)
{
remove();n
}
}
int length()const//O(1)
{
return m_length;
}
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 element to add");
}
}
T front()const
{
if(m_length>0)
{
return list_entry(m_header.next,Node,head)->value;
}
else
{
THROW_EXCEPTION(InvalidOperationException,"no element to get");
}
}
~LinkQueue()
{
clear();
}
};
}
#endif // LINKQUEUE_H
可以看出我们基于linux内核链表的队列变得更加高效了,链式队列入队操作和出队操作可以在常量时间内完成,如果对于LINUX那些接口不懂的可以查看LINUX链表大剖析文章