欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线性表--队列的数组实现

程序员文章站 2022-07-14 14:02:10
...
定义和应用

队列(queue)是一个线性表,其插入和删除分别在表的不同端进行。它是一种先进先出(FIFO)的线性表,插入元素的一端称为队尾(bakc或者rear),删除元素的一段叫作队首(front)。

以数组来实现,需要考虑空间的利用率。如果采用类似数组和栈一般的映射公式location(i)=i,那么每次删除数组前端(队首)元素需要将数组整体左移一位,显然效率低下。考虑新的映射公式location(i)=location(队首元素)+i,能很好地解决这一问题,但同时也有了一个新的问题,那就是在一系列的插入操作后,数组前端(队首位置)存在大量未使用的空地址,因此空间利用率低,如果将数组中所有元素整体左移,那么和刚才的方法一样,时间复杂度又为渐近大于arrayLength。

因此我们考虑将数组视为环,来实现环形的队列。环形数组实现的队列的映射公式为 location(i)=(location(队首元素)+i)%arrayLength 。环形队列也有一个问题,那就是queueFront==queueBack时,到底是表示队空还是队满呢?因此我们改变队首元素指针queueFront的约定,让他指向队首元素的前一个位置,这样当且仅当queueFront==queueBack时才表示队空,而如果 (queueBack+1)%arrayLength==queueFront时,就需要加倍数组并复制原有元素了。因此,该实现中,队列的元素个数最多是arrayLength-1个。

具体实现

queueADT.h

#pragma once
template <typename T>
class queueADT {
public:
	virtual ~queueADT(){ }
	virtual bool empty()const = 0;
	virtual size_t size()const = 0;
	virtual T& front()const = 0;//返回队首元素的引用
	virtual T& back()const = 0;//返回队尾元素的引用
	virtual void pop() = 0;//在队首删除元素
	virtual void push(const T& _element) = 0;//在队尾插入元素
};

queueEmpty.h

#pragma once
#include <string>
using std::string;
#include <iostream>
using std::cin; using std::cout; using std::cerr; using std::ostream; using std::istream;
using std::ends; using std::endl;
class queueEmpty {
public:
	queueEmpty(string _message = "Invalid operation on empty queue!") :message(_message) { }
	~queueEmpty() { }
	string what()const { return message; }
	void output()const { cerr << message << endl; }
private:
	string message;
};

queueArray.h

#pragma once
#ifndef queueArray_H
#define queueArray_H
#include "queueADT.h"
#include "queueEmpty.h"

template <typename T>
class queueArray :public queueADT<T> {
public:
	queueArray(size_t _capacity=10):
		queue(new T[_capacity]),capacity(_capacity),queueFront(0),queueBack(0){ }
	~queueArray() { delete[] queue; }

	bool empty()const { return queueFront == queueBack; }
	size_t size()const { return (queueBack - queueFront + capacity) % capacity; }
	T& front()const;
	T& back()const;
	void pop();
	void push(const T& _element);
	void output();

private:
	T* queue;		//元素数组
	size_t capacity;	//队列的容量capacity
	size_t queueFront;	//队首元素的前一个位置的下标
	size_t queueBack;	//队尾元素下标
};

template <typename T>
inline T& queueArray<T>::front()const {
	if (queueFront == queueBack)
		throw queueEmpty("Can not get front element on empty queue!");
	return queue[(queueFront + 1) % capacity];
}

template <typename T>
inline T& queueArray<T>::back()const {
	if (queueFront == queueBack)
		throw queueEmpty("Can not get back element on empty queue!");
	return queue[queueBack];
}

template <typename T>
inline void queueArray<T>::pop() {
	if (queueFront == queueBack)
		throw queueEmpty("Can not pop on empty queue!");
	queueFront = (++queueFront) % capacity;
	queue[queueFront].~T();
}

template <typename T>
void queueArray<T>::push(const T& _element) {
	//因为当且仅当queueFront==queueuBack的时候,队列为空。如果允许容器装满元素,那么这时候
	//也存在queueFront==queueuBack的情况。所以我们不能允许容器盛满元素。
	if ((queueBack+1)%capacity==queueFront) {//需要翻倍
		T* newQueue = new T[2 * capacity];
		//判断原数组中元素是否分段了 即Front指向的空元素的空间是否将其他数据隔开了
		size_t start = (queueFront + 1) % capacity;
		//因为首元素的下标也是(queueFront + 1) % capacity,因此也可以理解为:首元素是否在queue[0]或者queue[1]
		//上,如果不在的话则说明数据被首元素的前一个位置的queue[Front]给隔开了,形成了环
		if (start < 2) {//没有隔开 未形成环
			//直接把连续的数据拷贝到新数组中
			std::copy(queue+start,queue+start+capacity-1,newQueue);//注std::copy()拷贝范围是[first,last)
		}
		else {//被隔开了 队列形成了换
			std::copy(queue+start,queue+capacity-1,newQueue);//拷贝原数组前半段数据
			std::copy(queue,queue+queueFront,newQueue+capacity-start);//拷贝原数组后半段数据
		}
		//更新数据成员
		queueBack = capacity - 2;
		capacity *= 2;
		queueFront = capacity - 1;
		delete[] queue;
		queue = newQueue;
	}
	queueBack = (++queueBack) % capacity;
	queue[queueBack] = _element;
}

template <typename T>
void queueArray<T>::output() {
	size_t iter = 0;
	size_t pos = queueFront;
	while (iter<size()) {
		pos = (++pos) % capacity;
		cout << queue[pos] << " ";
		++iter;
	}
	cout << endl;
}
#endif

测试代码

#include "queueArray.h"
#include "..//..//..//ch06/ChainList/ChainList/illegalParameterValue.h"
#include "..//..//..//ch08/Stack/Stack/stackArray.h"

//===================================test for queueArray===================================
int main(){
	queueArray<double> q(5);
	if(q.empty())
		cout << "first,q.size()=" << q.size() << endl;
	q.push(1);
	cout << "rear of queue=" << q.back() << endl;
	q.push(2);
	cout << "rear of queue=" << q.back() << endl;
	q.push(3);
	cout << "rear of queue=" << q.back() << endl;
	q.push(4);
	cout << "rear of queue=" << q.back() << endl;
	q.push(5);
	cout << "rear of queue=" << q.back() << endl;
	q.output();
	cout << "after pop,q.size()=" << q.size() << endl;
	while (!q.empty()){
		cout << "pop the front element:" << q.front() << endl;
		q.pop();
	}
	try {
		q.pop();
	}
	catch(const queueEmpty& e){
		cout << "now q.size()=" << q.size() << endl;
		cout << e.what() << endl;
	}
}