C++数据结构与算法(八) 队列及队列的应用

队列和栈一样,是一种特殊的线性表,队列的删除和插入操作在队列两端进行,所以队列是一个FIFO的结构。

实现用数组描述的双端队列数据结构

定义:队列是一种特殊的线性表,队列的删除和插入操作在队列两端进行,插入端为队尾,删除元素的那一端称为队首。

队列的ADT如下:

#ifndef QUEUE_ABC_H
#define QUEUE_ABC_H

// 定义抽象类
template<typename T>
class queueABC
{
	public:
	// virtual ~queueABC();
	virtual bool empty() const=0;  // 纯虚函数 只读
	virtual int size() const=0;    // 返回队列中元素的个数   
	virtual T& front() = 0;        // 返回队首元素
	virtual T& back() = 0;         // 返回队尾元素
	virtual void pop() = 0;        // 删除队首的元素
	virtual void push(T x) = 0;    // 队尾插入元素 
};
#endif

数组描述方法:

方案一:

queuefront: 队首元素所在的位置

queueback:   队尾元素所在的位置

插入元素时,queueback+1, 将新元素插入,时间按复杂度

删除元素时,数组中的元素整体向左移动一位,时间按复杂度

方案2:

插入元素时,如果数组左端有空位,数组中的元素整体向左移动一位,然后再插入。如果没有空位,queueback+1,插入元素,最坏情况下,时间复杂度

删除元素时,queuefront+1,时间按复杂度

上述两个方案,如果删除的效率高,插入的效率就会低。插入的效率高,删除的效率就会低。

解决方案:

将队列的两端环接,在数组长度不变的情况下,插入和删除操作的时间复杂度均为

把数组视为一个环,而不是一条直线。

队列中位置arrayLength的下一个位置是0

location(i) = (location(队首元素位置)+i)%arrayLength

queuefront沿逆时针方向,指向队列首元素的下一个位置,queueback指向队列的最后一个位置。

当且仅当queuefront=queueback=0时,队列为空。但是当队列插满的时候,也有queuefront=queueback,导致无法判断队列是满还是空,所以约定队列不能插满。插入操作时先检查插入是否会使队列变满,如果是,则要增加数组长度。

类queue的实现:
queue.h文件:

#ifndef QUEUE_H
#define QUEUE_H

#include <iostream>
#include "E:\back_up\code\c_plus_code\dequeue\external_file\queueABC.h"   // 包含ABC文件 
#include "E:\back_up\code\c_plus_code\dequeue\external_file\queueemptyEx.h"   // 包含异常类文件 
using namespace std;

template<typename T>
class queue : public queueABC<T>
{
	private:
	int arrayLength;  // 数组的长度
	int queueSize;   // 队列中元素的个数
	int queueFront;   // 队首元素所在的位置
	int queueBack;    // 队尾元素所在的位置
	T* element; 
	void ensureArrayLength();   // 进行数组扩容 
	
	public:
	queue(int arrayLength=10);    // 构造函数 
	~queue();                     // 析构函数 
	queue(const queue& q);        // 拷贝构造函数 
	
	// ADT 
	bool empty() const;
	int size() const;
	T& front();
	T& back();
	void pop();
	void push(T x);
	void display_queue() const;   // 打印输出队列中的元素 
	
};

template<typename T>
queue<T>::queue(int arrayLength)
{
		this->arrayLength = arrayLength;
		this->queueSize = 0;
		this->queueFront = 0;
		this->queueBack = 0;
		element = new T[arrayLength];
}

template<typename T>
queue<T>::~queue()
{
	delete [] element;
} 

template<typename T>
queue<T>::queue(const queue& q)
{
	arrayLength = q.arrayLength;
	queueSize = q.queueSize;
	queueFront = q.queueFront;
	queueBack = q.queueBack;
	element = new T[arrayLength];
	for(int i=0; i<queueSize; i++)
	{
		element[i] = q.element[i];
	}
} 

template<typename T>
bool queue<T>::empty() const
{
	return queueSize==0;
} 

template<typename T>
int queue<T>::size() const
{
	return queueSize;
}

template<typename T>
T& queue<T>::front()
{
	return element[(queueFront+1)%arrayLength];  // queueFront是队首元素的前一个位置,+1是表示队首元素的位置 
}

template<typename T>
T& queue<T>::back()
{
	return element[queueBack%arrayLength];    // queueback队尾元素的位置 
}

template<typename T>
void queue<T>::ensureArrayLength()
{
	// 如果需要,增加数组长度 
	if((queueBack+1)%arrayLength==queueFront)   // 环形的数组 
	{
		T* old;
		old = element;
		delete element;
		// arrayLength = arrayLength*2;   // 增加分配的内存长度 
		element = new T[2*arrayLength]; 
		// 环形数组重新布局
		// 先将数组复制过来 
		for(int i=0; i<queueSize; i++)
		{
			if(i!=(queueFront)%arrayLength)   // 队列中这个位置没有值,下一个位置才是队列的首元素 
			{
				element[i] = old[i];
			}
			else
			{
				continue;
			}
		} 
		delete old;
		// 重新布局  // 分为三种情况:1.queuefront=0; queuefront==arrayLength-1; else;
		int pre_start = queueFront%arrayLength;  // 队列首元素的前一个位置 
		if(pre_start==0)
		{
			// 移动所有的数组元素 
			for(int i=0; i<queueSize; i++)
			{
				element[2*arrayLength-1-i] = element[queueBack%arrayLength-i];
			} 
			queueFront = queueFront + arrayLength;
			queueBack = queueBack + arrayLength;
		} 
		else if(pre_start==arrayLength-1)   // arrayLength还是原来的值 
		{
			// 不用移动数组元素,之更改front 
			queueFront = queueFront + arrayLength; 
		}
		else
		{
			// 移动第二段的元素:
			int element_to_move = arrayLength-1-(queueFront%arrayLength);  // 计算第二段需要移动的元素的数量 
			for(int i=0; i<element_to_move; i++)
			{
				element[2*arrayLength-1-i] = element[queueBack%arrayLength-i];
			} 
			queueFront = queueFront + arrayLength;
		}
		arrayLength = arrayLength*2;   // 更新arrayLength 		 
	}
	// 添加一个动态减少内存的函数 
}

// push()操作:
template<typename T>
void queue<T>::push(T x)
{
	ensureArrayLength();     // 先保证数组长度满足条件
	queueSize++;
	queueBack = (queueBack+1)%arrayLength;
	element[queueBack] = x;	 
} 

//pop()操作 
template<typename T>
void queue<T>::pop()
{
	if(empty())
        throw queueEmptyException(0); 
	queueFront++;
	queueSize--;
	// element[queueFront].~T;
} 

template<typename T>
void queue<T>::display_queue() const
{
	//int tmp = queueFront;
	// int start_pos = (tmp+1)%arrayLength;
	if(empty())
	{
		cout << "The queue is empty!" << endl;
		return;
	}
	
	for(int i=0; i<queueSize; i++)
	{
		cout << element[(queueFront+1+i)%arrayLength] << " ";
	}
	cout << endl;
}

#endif 

在队列进行pop()操作的时候,需要判断队列是否为空,如果为空,需要抛出异常,这一功能由queue_empty_exception实现:

// 自定义异常类
#ifndef QUEUE_EMPTY_EXCEPTION
#define QUEUE_EMPTY_EXCEPTION
#include <stdexcept>
#include <iostream>
using namespace std;

class queueEmptyException : public runtime_error
{
	private:
	int queueSize;
	
	public:
	queueEmptyException(int queueSize) : runtime_error("The queue empty!")
	{
		this->queueSize = queueSize;
	}
	
	void display_error_info()
	{
		cout << "The queue size is " << queueSize << endl;
	}
};
#endif

测试代码:

main.cpp

#include <iostream>
#include "E:\back_up\code\c_plus_code\dequeue\external_file\queue.h"
using namespace std;

int main(int argc, char *argv[])
{
	cout<<"Hello C-Free!"<<endl;
	queue<int> q1;
	try    // 测试异常类 
	{
		q1.pop();
	}
	catch(queueEmptyException& ex)
	{
		cout << ex.what() << endl;
		ex.display_error_info();
	}
	for(int i=0; i<15; i++)
	{
		q1.push(i+1);
	}
	q1.push(22);
	q1.push(34);
	q1.pop();
	q1.pop();
	q1.pop();
	
	while(!q1.empty())
	{
		cout << q1.front() << " ";
		q1.pop(); 
	}
	cout << endl;
	
	for(int i=0; i<7; i++)
	{
		q1.push(i*2);
	}
	q1.push(100);
	q1.pop();
	q1.display_queue();
	
	return 0;
}

运行结果:

函数ensureArrayLength的共工作方式:

在队列中元素的数量超过数组的长度时,需要为队列分配一个更大的数组,这里采取数组长度加倍的操作,需要将原来的数组中的内容分配到新的数组中。再复制完数组后,需要对原来队列的queueFront和queueback参数进行修改,因为队列的数组是按照环形的方式来处理的,queueFront和queueback参数能够推算出队列中的元素在数组中的实际位置(即索引值)。

在环形数组展开之后,会出现三种元素分布的方式:

(a).queueFront位于数组的第一个位置

(b) queueFront位于数组的最后一个位置

(c) queueFront位于数组的其他位置

在三种不同的操作下,需要将就数组复制到新数组的操作不同,且queueFront和queueback参数的变化也不相同,具体见代码所示。

template<typename T>
void queue<T>::ensureArrayLength()
{
	// 如果需要,增加数组长度 
	if((queueBack+1)%arrayLength==queueFront)   // 环形的数组 
	{
		T* old;
		old = element;
		delete element;
		// arrayLength = arrayLength*2;   // 增加分配的内存长度 
		element = new T[2*arrayLength]; 
		// 环形数组重新布局
		// 先将数组复制过来 
		for(int i=0; i<queueSize; i++)
		{
			if(i!=(queueFront)%arrayLength)   // 队列中这个位置没有值,下一个位置才是队列的首元素 
			{
				element[i] = old[i];
			}
			else
			{
				continue;
			}
		} 
		delete old;
		// 重新布局  // 分为三种情况:1.queuefront=0; queuefront==arrayLength-1; else;
		int pre_start = queueFront%arrayLength;  // 队列首元素的前一个位置 
		if(pre_start==0)
		{
			// 移动所有的数组元素 
			for(int i=0; i<queueSize; i++)
			{
				element[2*arrayLength-1-i] = element[queueBack%arrayLength-i];
			} 
			queueFront = queueFront + arrayLength;
			queueBack = queueBack + arrayLength;
		} 
		else if(pre_start==arrayLength-1)   // arrayLength还是原来的值 
		{
			// 不用移动数组元素,之更改front 
			queueFront = queueFront + arrayLength; 
		}
		else
		{
			// 移动第二段的元素:
			int element_to_move = arrayLength-1-(queueFront%arrayLength);  // 计算第二段需要移动的元素的数量 
			for(int i=0; i<element_to_move; i++)
			{
				element[2*arrayLength-1-i] = element[queueBack%arrayLength-i];
			} 
			queueFront = queueFront + arrayLength;
		}
		arrayLength = arrayLength*2;   // 更新arrayLength 		 
	}
	// 添加一个动态减少内存的函数 
}

队列的应用:

在文章栈的应用中,使用栈结构实现了列车车厢重排序问题:https://blog.csdn.net/zj1131190425/article/details/88086003

现在,列车车厢重排序问题,这次的缓冲轨道为队列,一个FIFO的结构,如下图所示:

代码实现:

#include <iostream>
#include "E:\back_up\code\c_plus_code\dequeue\external_file\queue.h"
using namespace std;

int cache_track_num = 3;   // 缓冲轨道的数量 
//queue<int>* H = new queue<int>[cache_track_num]; 
queue<int> H[3];
queue<int> in_rail;
queue<int> out_rail;
 
void showRailState()   // 显示缓冲轨道及出入轨道的车厢序号 
{
	cout << "=================================" << endl;
	
	cout << "In rail state:";
	in_rail.display_queue();
	
	cout << "H1 state: ";
	H[0].display_queue();
	
	cout << "H2 state: ";
	H[1].display_queue();
	
	cout << "H3 state: ";
	H[2].display_queue();
	
	cout << "Out rail state: ";
	out_rail.display_queue();
 
}

// 缓冲轨道上的处理
void cache_rail_process(int& cnt_number, int n1=10)    // 参数cnt_number为需要在缓冲车厢查找的车厢编号 
{
	if(cnt_number<=n1)
	{
		for(int i=0; i<cache_track_num; i++)
		{
			if(!H[i].empty() && H[i].front()==cnt_number)
			{
				out_rail.push(H[i].front());
				H[i].pop();
				cache_rail_process(++cnt_number);
			}
		}
	} 
} 


void rail(int train_number[], int n)  // 参数:车厢初始顺序,车箱数
{
	for(int i=0; i<n; i++)     // 初始化入轨道 
	{
		in_rail.push(train_number[i]);
	}
	
	int train_number_cnt = 1;   // 当前需要寻找的车厢编号
	
	while(!in_rail.empty())
	{
		showRailState();
		int tmp_number = in_rail.front(); 
		in_rail.pop();
		
		if(tmp_number == train_number_cnt)    // 如果出队列的车厢是刚好要寻找的车厢:
		{
			out_rail.push(tmp_number);  // 直接放入出轨道 
			train_number_cnt++;
			
			// 现在需要在缓冲轨道上选找是否有满足条件的车厢,通过递归的方式
			cache_rail_process(train_number_cnt, n); 
			 
		}
		else    // 放入缓冲轨道 
		{
			// 如果缓冲均为空轨道
			if(H[0].empty() && H[1].empty() && H[2].empty())
			{
				H[0].push(tmp_number);
			} 
			else   // 有非空的缓冲轨道,优先寻找非空的可放置的缓冲轨道 
			{
				int trace_flag[cache_track_num];
				int non_empty_placable[] = {0, 0, 0};    // 非空且可放置的位置 
				for(int k=0; k<cache_track_num; k++)
				{
					if(H[k].empty())
					{
						trace_flag[k] = 0;
					}
					else
					{
						trace_flag[k] = tmp_number - H[k].back();   // 队列尾部的元素要小于需要放置的元素
						if(trace_flag[k]>0)
						{
							non_empty_placable[k] = 1;
						} 
					}
				} 
				
			    int non_placable_cnt = 0;
				for(int k=0; k<cache_track_num; k++)
				{
					if(non_empty_placable[k]==1)
					{
						non_placable_cnt++;
					}
				}
				
				if(non_placable_cnt==0)    // 非空可放置的位置为0.放入空位 
				{
					for(int k=0; k<cache_track_num; k++)
					{
						if(trace_flag[k]==0)
						{
							H[k].push(tmp_number);
							break;   // 放一次 
						}
					} 
				}
				
				else if(non_placable_cnt==1)   // 一个非空可防止的位置
				{
					for(int k=0; k<cache_track_num; k++)
					{
						if(non_empty_placable[k]==1)  // 当前位置,唯一满足条件的
						{
							H[k].push(tmp_number);
							break;
						} 
					}
				}
				else   // 有多个非空可放置的位置 
				{
					int cmp = 100;
					int cmp_index = 0;
					for(int k=0; k<cache_track_num; k++)
					{
						if(trace_flag[k]>0 && trace_flag[k]<cmp)
						{
							cmp = trace_flag[k];
							cmp_index = k;
						}
					} 
					H[cmp_index].push(tmp_number);
				} 
				
			}
			
		}
		//showRailState();	
	}
	showRailState(); 
	
} 


int main(int argc, char *argv[])
{	
    int carNumber[] = {5,8,1,7,4,2,9,6,3}; 
    int nn = 9;
    rail(carNumber, nn);
	return 0;
}

测试结果:

----------------------------------------------------------------------------------------------------------------------------

猜你喜欢

转载自blog.csdn.net/zj1131190425/article/details/88090905