今天学习数据结构中的队列。
首先,队列也是一种特殊的线性表,前面说到栈也是一种特殊的线性表,所以我们对比着学习队列的相关知识和操作。
我们知道栈只能操作线性表的某一端,所以说它特殊;队列也特殊,它特殊在仅能在线性表的两端操作,而且入队和出队各在一端,不能混淆。
队列有两端:队头和队尾。
队头:取出数据元素的一端。
队尾:插入数据的一端。
队列有一个它独有的特性--先进先出。
数据结构中的队列和生活中的队列模型是一致,先入队的先出队。
知道了队列的模型后就该进行队列的一系列操作了,队列也有其基本的增删改查操作。
创建队列(Queue())、销毁队列(~Queue())、清空队列(clear())、进队(add())、出队(remove())、获取队头(front())、获取队列长度(length())。
队列分为两种,顺序队列和链式队列,基于这个原因我们设计一个队列的抽象父类Queue,具体行为在各自的类中实现。
抽象父类设计要求:
由于可能有各种类型的数据,所以需要模板编程。
继承自顶层父类Object。
队列操作函数全部为纯虚函数。
获取队头和队列长度函数应为const类型以供const对象调用。
下面是Queue类的具体实现:
template <typename T>
class Queue : public Object
{
public:
virtual void add(const T& obj) = 0;
virtual void remove() = 0;
virtual T front() const = 0;
virtual void clear() = 0;
virtual int length() const = 0;
};
实现顺序队列的关键点在于入队和出队后对队头标识和队尾标识的处理,我们采用循环计数法,就是动态的调整队头和队尾的位置标识。
模板编程。
继承自抽象父类Queue。
原生数组作为队列的存储空间。
最大容量由模板参数决定。
增加保护成员变量:长度信息变量m_length、队头位置标识变量m_front、队尾位置标识m_rear。
具体实现如下:
template <typename T, int N>
class StaticQueue : public Queue<T>
{
protected:
T m_space[N];
int m_rear;
int m_front;
int m_length;
public:
StaticQueue()
{
m_front = 0;
m_rear = 0;
m_length = 0;
}
int capacity()
{
return N;
}
void add(const T& obj)//使用循环计数法可以使对头队尾不固定,提高效率
{
if(m_length < N)
{
m_space[m_rear] = obj;
m_rear = (m_rear + 1) % N;//循环计数法
m_length++;
}
else
{
THROW_EXCEPTION(InvalidOperationException,"no space to add...");
}
}
void remove()//每次出队列后,原先的元素之后那个元素就作为新的队头
{
if(m_length > 0)
{
m_front = (m_front + 1) % N;//循环计数法
m_length--;
}
else
{
THROW_EXCEPTION(InvalidOperationException,"no element to remove...");
}
}
T front() const
{
if(m_length > 0)
{
return m_space[m_front];
}
else
{
THROW_EXCEPTION(InvalidOperationException,"no memory to front...");
}
}
void clear()
{
m_front = 0;
m_rear = 0;
m_length = 0;
}
int length() const
{
return m_length;
}
};
在入队和出队中,我们使用了循环计数法来动态确定队头队尾的位置,这样就提高了操作效率,队头不是固定的意味着我们在每次出队后不需要移动大量数据元素来填补队头的位置,这样效率就上去了。
顺序队列其实也是有缺陷的,和顺序栈的缺点一样,当数据元素为类类型时,StaticQueue的对象在创建时会多次调用类的构造函数,影响效率。
链式队列1--LinkQueue
类模板编程。
继承自抽象父类Queue。
链式队列的实现可以组合使用单链表对象。
只在链表的头部和尾部进行操作。
可以根据队列的特性参照链式栈的实现。
这种实现在入队时的开销还是蛮大的,每次入队都需要遍历整个队列,所以他是低效的。
链式队列2--DualCircleQueue
基于双向循环链表实现的队列就弥补了每次插入都便利整个队列的缺点,头节点的pre指针指向的是队尾,头节点的next指针指向的就是队头,操作头节点的pre指针或next指针就可以直接操作队列的头部和尾部,避免了遍历队列,这样也提高了效率。
总结:
StaticQueue在初始化时可能多次调用构造函数。
LinkQueue的组合使用能够实现队列的功能,但是不够高效。
DualCircleQueue组合使用了双向循环链表,入队和出队操作可以在常量时间内完成。
在此感谢狄泰唐老师。