数据结构——循环顺序队列

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];
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值