队列是一种特殊的线性表。
队列仅能在线性表的两端进行操作:
队头(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中入队和出队的操作可以在常量时间内完成。