1.介绍
队列其实在生活中有很多例子,比如说看电影买票需要排队,上公交车需要排队刷卡。
这种队列就是前头出,后头进的结构,即先进先出(FIFO)。
因此,我们只能对队首和队尾进行操作,如果你插队操作的话,别人会说你不守公德。
实际中,我们分别用head和tail两个指针分别管理队首和队尾。
普通顺序队列容易假溢出,即只要当前面的元素出队列,head指针就会向后指,就再也不会回头。
这样,前面的一段空间实际上并没有用上,因此普通顺序队列实际意义并不大。
而且如果,我们每次将元素出队列,将后面的元素向前面复制,那么维持类似顺序表的结构时间开销是很大的。
因此,我们一般采用循环列表,循环列表可以想象成一个圆,这样两个指针就可以在一段连续的存储空间来回指向。
类似于这样:
现在来看具体操作如何实现。
2.操作
下面都是基于动态存储的顺序栈,其实链式队列是用不着循环的。
(1)创建
head指针指向队首元素,而tail指向队尾元素后一个位置。因此,初始时我们可以将head和tail初始为0。
head只有在和tail相等时,才和它的意义不同,因为我们要知道此时队列使用长度,只需要用(tail-head+maxSize)%maxSize就可求得。
至于这个公式,需要数论的知识,但可以拿家里的钟用几个数字实验一下。
也可以拿下面两个图试一下。
因此,head和tail相等,这时的长度是为0的。
template<typename T>
Queue<T>::Queue()
{
head = tail = 0;
data = new T[10];
maxSize = 10;
}
(2)判空
template<typename T>
bool Queue<T>::isEmpty()
{
if (head == tail)
return true;
return false;
}
(3)判满
因为,我们的tail指向队尾之后的元素,而一旦这个元素就是head的话,判空函数就会一直为true,不能出队列,因此我们规定tail所指元素不能存放元素。
也就是maxSize个存储空间,只能存放maxSize-1个元素。
template<typename T>
bool Queue<T>::isFull()
{
if ((tail + 1) % maxSize == head)
return true;
return false;
}
其实,也有其他的判断方法:
1.再设置一个length变量,每当出队就减一,进队就加一。
2.只有最近进队后,才会满队。只有最近出队后,才会空队。设置一个变量tag,在进队和入队时,分别设置特点标记数字即可。
(4)出队
出队和进队就在于循环二字,head=(head+1)%maxSize;和tail = (tail + 1) % maxSize;这两个语句理解,两个操作也就基本明了。
template<typename T>
bool Queue<T>::pop()
{
if (isEmpty())
return false;
head=(head+1)%maxSize;
return true;
}
(5)进队
template<typename T>
void Queue<T>::push(const T& val)
{
if (isFull())
resize();
data[tail] = val;
tail = (tail + 1) % maxSize;
return;
}
(6)查看队首元素
template<typename T>
const T& Queue<T>::getFirst()
{
if (isEmpty())
throw("error");
return data[head];
}
(7)扩容
动态存储的扩容是不能简单的复制的。
正常情况下,head<tail时可以正常复制。但当head>tail时,复制过去就会出错。如下:
我们扩容了一个单位,但当出队后,发现就会出错,因为我们的队列在满的情况下,也有head==tail。我们的判空函数也就会一直为true,不能再出栈。
而里面明明有元素存进,因此,不能简单的复制进去。
template<typename T>
void Queue<T>::resize()
{
T* p = data;
data = new T[maxSize*2];
//无论是tail>head还是tail<head都把他复制到新的内存,新的内存从0开始
for (int i = head; ((i + maxSize) % maxSize) < tail; i++)
data[i - head] = p[i];
head = 0;
tail = maxSize - 1;
maxSize *= 2;
delete[]p;
}
我们会将从head到tail之间的所有元素重新列在新内存0到maxSize之间。这样,队列的结构也就维持了。
总的代码如下:
#pragma once
template<typename T>
class Queue
{
T* data;
int head;//head队首指针指向队首元素
int tail;//指向队尾后一个位置
int maxSize;
void resize();
public:
Queue();
~Queue();
bool isEmpty();
bool isFull();
void push(const T& val);
bool pop();
const T& getFirst();
};
template<typename T>
void Queue<T>::resize()
{
T* p = data;
data = new T[maxSize*2];
//无论是tail>head还是tail<head都把他复制到新的内存,新的内存从0开始
for (int i = head; ((i + maxSize) % maxSize) < tail; i++)
data[i - head] = p[i];
head = 0;
tail = maxSize - 1;
maxSize *= 2;
delete[]p;
}
template<typename T>
Queue<T>::Queue()
{
head = tail = 0;
data = new T[10];
maxSize = 10;
}
template<typename T>
Queue<T>::~Queue()
{
delete[]data;
}
template<typename T>
bool Queue<T>::isEmpty()
{
if (head == tail)
return true;
return false;
}
template<typename T>
bool Queue<T>::isFull()
{
if ((tail + 1) % maxSize == head)
return true;
return false;
}
template<typename T>
void Queue<T>::push(const T& val)
{
if (isFull())
resize();
data[tail] = val;
tail = (tail + 1) % maxSize;
return;
}
template<typename T>
bool Queue<T>::pop()
{
if (isEmpty())
return false;
head=(head+1)%maxSize;
return true;
}
template<typename T>
const T& Queue<T>::getFirst()
{
if (isEmpty())
throw("error");
return data[head];
}