基本概念
队列定义及特点
- 队列(queue)是一种只能在某一段进行插入,在另一端进行删除的线性表。
- 将进行插入和删除的两端分别称为队首(front)和队尾(rear)。
- 将元素进入队列成为入队(enqueue),将元素从队列中删除成为出队(dequeue)。
- 队列具有FIFO(First In, First Out)的特性。
队列的基本操作
名称 | 作用 |
---|---|
create() | 创建一个空的队列 |
destroy(q) | 销毁一个队列q |
empty(q) | 判断队列q 是否为空 |
enqueue(q, v) | 将元素v 进入队列q |
dequeue(q) | 将队列q 的首元素出列 |
front(q) | 返回队列q 的首元素 |
队列的实现
同栈一样,队列也是一种特殊的线性表。因此利用线性表的基本操作也能够实现一个队列。
利用顺序存储结构实现队列,直接使用数组较为便利,因此这里没有像栈的实现那样利用之前实现的线性表了。
利用顺序存储结构存储队列的问题在于如何在
O(1)
的时间内实现入队和出队。因为队列的容量
c
是固定的,因此我们可以维护两个指针,分别表示队首和队尾。只要入队,则将队首指针front
向后移动一位,出队,则需要将队尾指针rear
向后移动一位。为了实现空间的有效利用,必须实现环形队列,也就是指针可以从结尾跳到开头(circular queue)。所幸的是,可以使用求余操作实现这个目的,即
/**
* 顺序队列类
*/
template<typename _Ty, int capacity = 64>
class SeqQueue{
private:
_Ty _data[capacity];
int _front;
int _rear;
int _length;
public:
/**
* 构造函数,创建一个空队列
*/
SeqQueue():_front(0), _rear(0), _length(0){}
/**
* 析构函数
*/
~SeqQueue(){}
/**
* 判断队列是否为空
* @return bool 空栈返回true,否则false
*/
bool empty(){
return _length == 0;
}
/**
* 返回栈顶元素
* @return _Ty & 栈顶元素
*/
_Ty & front(){
assert(!this->empty());
return _data[_rear];
}
/**
* 将元素入列
* @param _Ty & value 需要入列的元素
*/
void enqueue(_Ty value){
assert(_length < capacity);
_data[_front++ % capacity] = value; //注意后++不是前++
_length++;
}
/**
* 将队首元素出列
*/
void dequeue(){
assert(!this->empty());
_rear = (_rear++) % capacity;
_length--;
}
/**
* 返回队列的实际长度
* @return int 队列的实际长度
*/
int length(){
return _length;
}
};
利用链式存储结构实现队列,除了需要维护头指针外,还需要维护尾指针。这样在入队时,可以将其插入尾部,而出队,则将head->next
(第一个结点)删除即可。
至于STL的queue
和stack
一样,也是用deque
这种容器实现的一种配接器。STL的deque
的存储结构比较复杂,它保证了插入和删除的摊还时间都是
队列的应用
队列在数据结构和算法中的应用也很多,比较经典的就是图的广度优先搜索(Broaden First Search)。
对于广搜的简要描述如下:
取一个顶点v_0进入队列q
while q不空
f = q.front
检查f的下一个可达且未走过的顶点集合V
foreach v in V
q.enqueue(v)
标记f已经走过
q.dequeue
队列还有很多在其他方面的应用,比如操作系统、网络、分布式等,其核心在于资源分配和任务调度。
除此之外,队列也能够模拟一些实际中的类似于队列的问题。
Summary
队列的基本特性
队列是一种特殊的,操作受限的线性表。只能在一端进行插入,在另一端进行删除。这种FIFO的特性保证了队列只能够按照原序输出。
队列的实现
队列作为线性表,同样有顺序存储和链式存储两种存储结构。使用链式存储结构时,需要保留尾指针,以便在 O(1) 时间内进行操作;使用顺序存储结构时,为了提高空间利用率,通常使用求余法构造环形队列。
队列的应用
队列的一个重要应用就是实现BFS。除此之外,队列在操作系统等方面,涉及到任务调度和资源分配时,也有着非常多的应用。普通队列严格执行FIFO特性,而后面的优先队列(利用binary heap实现)则按照优先级出队。