1、队列的概念
和堆栈一样,队列也是一种特殊的线性表。与堆栈不同的是,队列的插入和删除操作分别在线性表的两端进行,是一种先进先出的线性表。将添加新元素的那一端称为队尾(rear),删除元素的一端称为队首(front)。队列的相关操作见下图(图片来源于参考文献[1])
上图中:
- 图a)表示了一个具有3个元素的队列,其中front指向队首,rear指向队尾;
- 图b)展示了队列的删除元素操作,将原先的队首元素A删除,并且front指向了下一个元素B;
- 图c)展示了队列的添加元素操作,在队尾添加了元素D,并且rear指向了队尾元素D。
本文将利用公式化描述的方法和链表的方法来描述队列。
2、公式化描述的队列
2.1 描述队列公式的选择
在参考文献[1]中,分别分析了一下三个公式:
文中主要从元素的添加和删除操作的时间效率方面进行了分析,现加以总结。
- 对于公式1,考虑方式同普通的数组,初始时索引值从0~n-1。
(1)执行添加元素操作,即在队尾添加一个元素,直接可添加到location=n的位置,接着将real指向添加元素即可,时间复杂度为 O(1) 。
(2)执行删除操作,即删除队列首元素,将首元素删除后,剩余元素的索引值必须满足公式1,从而必须将之前的n-1个元素依次前移一个位置。时间复杂度为 Θ(n−1) 。 - 对于公式2,考虑初始时具有n个元素。队首元素location=location(1),队尾元素location=location(1)+n-1。
(1)如若执行删除操作,直接将队首元素删除,并将front指向下一个元素即可,时间复杂度为 O(1) 。
(2)执行添加操作,将元素添加至当前real的下一个位置,并将real指向最后一个元素。考虑队列最大容量有限,那么在添加元素时,会遇到real=maxSize的情形,此时,若执行了队列的删除操作,在队列前部会有空余空间,若想充分利用此空间,则必须将所有的元素移到队首部分,此时时间复杂度为 Θ(m) ,其中m为队列中需要移动的元素个数。 - 对于公式3,比公式2多了“%maxSize”部分,单独考虑这部分可知,当
location(1)+i−1
的值大于maxSize时,不会报出内存不足异常,若队列前部有剩余空间,会接着向队列中添加元素,并且元素索引满足公式3。太巧妙了!!!!!!但是,,这里要考虑一个极端情况,real的值会大于或等于front。为了避免此种情况,限制队列的最大长度=最大容量-1,即队列不会被填满。
对于公式3,可借助于下图理解。
2.2 队列公式化描述的C++实现
下面开始撸代码。。。。。。
2.2.1 类的声明与简单函数的实现
/*--------------------------基于公式Queue(i)=(Queue(1)+i-1)%MaxQueue描述的队列---------------------------*/
template <class T>
class Queue
{
public:
Queue(int MaxSize = 10);
~Queue() { delete[] element; };
bool IsEmpty() const //队列是否为空
{
return Rear == Front;
}
bool IsFull() const //队列是否已满
{
return ((Rear + 1) % MaxQueueSize== Front )? 1 : 0;
}
int Length() const; //队列中元素个数
T getFront() const; //获得队列首元素
T getRear() const; //获得队列末元素
Queue<T> &Add(const T &x); //插入元素x
Queue<T> &Delete(T &x); //删除末尾元素,并赋给x
void Input(istream &in) //输入一个队列
{
int size;
cout << "请输入队列的大小: ";
cin >> size;
cout << "请输入队列元素: " << endl;
for(int i=0;i<size;i++)
{
T x;
in >> x;
Add(x);
}
cout << endl;
}
void Output(ostream &out)
{
while (!IsEmpty())
{
int x;
Delete(x);
out << x<< " ";
}
cout << endl;
}
private:
T *element; //队列元素指针
int MaxQueueSize; //队列最大容量
T Rear; //队列末元素
T Front; //队列前段,指向首元素的前一个位置
};
2.2.2 创建队列—构造函数实现
template <class T>
Queue<T>::Queue(int MaxSize=10)
{
//采用公式Queue(i)=(Queue(1)+i-1)%MaxQueue来描述队列,为了避免当real==front时,队列有可能为空或队列已满。
//将要求的队列大小+1后,使得real的最大值为MaxQueueSize-1=MaxSize,刚好能存储MaxSize个元素,且避免了real==front时
//带来的歧义。此时,当队列为空时,满足real==front;当队列满时,满足(real+1)%MaxQueueSize==front。
MaxQueueSize = MaxSize + 1;
element = new T[MaxQueueSize]; //新建队列元素数组
Rear = Front = 0; //初始时将Real和Front置零
}
2.2.3 获取队首元素
//获得队列首元素
template <class T>
T Queue<T>::getFront() const
{
if (!IsEmpty()) //队列不为空
return element[(Front+1)%MaxQueueSize];
else //队列为空
throw OutOfRange();
}
2.2.4 获取队尾元素
//获得队列末元素
template <class T>
T Queue<T>::getRear() const
{
if (!IsEmpty()) //队列不为空
return element[Rear];
else //队列为空
throw OutOfRange();
}
2.2.5 插入元素x
//插入元素x
template <class T>
Queue<T> &Queue<T>::Add(const T &x)
{
if (IsFull()) //若队列已满,则抛出内存不足异常
throw NoMerm();
else
{
//指向下一个位置,并赋值
Rear = (Rear + 1) % MaxQueueSize;
element[Rear] = x;
}
return *this;
}
2.2.6 删除元素,并赋给x
//删除队首元素,并赋给x
template <class T>
Queue<T> &Queue<T>::Delete(T &x)
{
if (IsEmpty()) //若队列为空,则抛出异常
throw OutOfRange();
else {
//获得队首元素,并将Front指向下一个位置
x = element[(Front + 1) % MaxQueueSize];
Front = (Front + 1) % MaxQueueSize;
}
return *this;
}
2.2.7 获取队列长度
//队列中元素个数
template <class T>
int Queue<T>::Length() const
{
if (IsEmpty())
return 0;
else if (IsFull())
return MaxQueueSize-1; //体现出公式3的精妙之处
else
return (Rear-Front);
}
2.2.8 输入输出运算符重载
//重载“>>”
template <class T>
istream &operator>>(istream &in, Queue<T> &Q)
{
Q.Input(in);
return in;
}
//重载“<<”
template <class T>
ostream &operator<<(ostream &out, Queue<T> &Q)
{
Q.Output(out);
return out;
}
2.2.9 测试
/*-------------------------------公式化描述的队列----------------------------------*/
//队列对象
Queue<int> Q(5);
Q.Add(1).Add(2).Add(3).Add(4);
int x;
//Q.Delete(x);
//Q.Add(6);
cout << "队首元素: "<<Q.getFront() << endl<< "队尾元素: "<<Q.getRear() << endl;
//cout << "删除的元素:" << x << endl;
cout << "队列长度: " << Q.Length() << endl;
Q.Add(7);
//Q.Delete(x);
cout << "队列长度: " << Q.Length() << endl;
//Queue<int> qq(10);
//cin >> qq;
//cout << qq << endl;;
3. 链表实现队列
链表实现队列时,原理很简单,就是将队列的限制条件(先进先出)应用于链表中,外加指针的相关操作。
3.3.1 链表队列类的声明及简单函数的实现
/*--------------------------基于链表描述的队列---------------------------*/
template <class T> class LinkedQueue;
//队列链表节点类
template <class T>
class Node
{
friend LinkedQueue<T>; //声明为LinkedQueue类的友类
public:
Node() {};
~Node() {};
private:
T data;
Node<T> *link;
};
//链表队列类
template <class T>
class LinkedQueue
{
public:
LinkedQueue() { front = rear = 0; };
~LinkedQueue();
bool IsEmpty() const //判断是否为空
{
return front == 0;
}
bool IsFull() const; //判断队列是否已满
T Frist() const; //获取队列首元素
T Last() const; //获取队列末尾元素
int Length(); //队列长度
LinkedQueue<T> &Add(const T &x); //添加元素
LinkedQueue<T> &Delete(T &x); //删除元素
void Input(istream &in) //输入链表队列
{
T x;
int size; //队列大小
cout << "请输入队列大小: "<<endl;
cin >> size;
cout << "请输入队列元素: " << endl;
while (size && !IsFull())
{
in >> x;
Add(x);
size--;
}
}
void Output(ostream &out) //输出链表队列
{
if (IsEmpty())
throw OutOfRange();
else
{
Node<T> *next=front;
while (next!= 0)
{
out << next->data<<" ";
next = next->link;
}
cout << endl;
}
}
private:
Node<T> *front; //头节点
Node<T> *rear; //末节点
};
3.2 删除链表队列—析构函数实现
//析构函数
template <class T>
LinkedQueue<T>::~LinkedQueue()
{
Node<T> *next=new Node<T>;
while (front) //未到队列末尾
{
next = front->link; //指向下一个节点
delete front; //删除当前节点
front = next; //循环
}
}
3.3 判断队列是否已满
//判断队列是否已满
template <class T>
bool LinkedQueue<T>::IsFull() const
{
Node<T> *p;
try {
p = new Node<T>; //若不能新建节点元素,则会引发new的异常NoMerm
delete p;
return false;
}
catch (NoMerm) //捕获异常,存在于Error.h文件中
{
return true;
}
}
3.4 获取队首元素
//获取队列首元素
template <class T>
T LinkedQueue<T>::Frist() const
{
if (IsEmpty()) //队列为空,抛出越界异常
throw OutOfRange();
else //队列不为空,返回首元素
{
return front->data;
}
}
3.5 获取队尾元素
//获取队列末尾元素
template <class T>
T LinkedQueue<T>::Last() const
{
if (IsEmpty()) //队列为空,抛出越界异常
throw OutOfRange();
else //队列不为空,返回队列末尾元素
return rear->data;
}
3.6 队列长度
//队列长度
template <class T>
int LinkedQueue<T>::Length()
{
if (front == rear)
return 0;
Node<T> *next=front;
int len = 0;
while (next)
{
next = next->link;
len++;
}
return len;
}
3.7 添加元素
//添加元素
template <class T>
LinkedQueue<T> &LinkedQueue<T>::Add(const T &x)
{
Node<T> *p=new Node<T>;
p->data = x;
p->link = 0;
if (front) //队列不为空,当前末节点指向p
rear->link=p;
else //队列为空
front=p; //p为队列首元素
rear = p; //更新队列末节点
return *this;
}
3.8 删除元素
//删除元素
template <class T>
LinkedQueue<T> &LinkedQueue<T>::Delete(T &x)
{
if (IsEmpty())
throw OutOfRange();
else
{
x = front->data;
Node<T> *next=front;
front = front->link;
delete next;
}
return *this;
}
3.9 重载输入输出运算符
//重载“>>”
template <class T>
istream &operator >> (istream &in, LinkedQueue<T> &Q)
{
Q.Input(in);
return in;
}
//重载“<<”
template <class T>
ostream &operator<<(ostream &out, LinkedQueue<T> &Q)
{
Q.Output(out);
return out;
}
3.10 测试
/*--------------------------------链表描述的队列----------------------------------*/
//队列对象
LinkedQueue<int> Q;
Q.Add(1).Add(2).Add(3).Add(4);
int x;
Q.Delete(x);
Q.Add(6);
cout << "队首元素: " << Q.Frist() << endl << "队尾元素: " << Q.Last() << endl;
cout << "删除的元素:" << x << endl;
cout << "队列长度: " << Q.Length() << endl;
Q.Add(7);
//Q.Delete(x);
cout << "队列长度: " << Q.Length() << endl;
LinkedQueue<int> LQ;
cin >> LQ;
cout << LQ;
参考文献:
[1] 数据结构算法与应用:C++描述(Data Structures, Algorithms and Applications in C++ 的中文版)