常用数据结构——队列及其应用

队列和栈作为一种最简单最基本的常用数据结构,可以说在许多方面都应用广泛。在程序运行时他们可以保存程序运行路径中各个点的信息,以便用于回溯操作或其他需要访问已经访问过的节点信息的操作。这里对队列的特点、作用做出描述、并简单地用不同途径实现了队列的基本功能。本文的代码实现均为类C语言(节点用结构体封装,部分语法为C++,比如引用),或者纯C++。


什么是队列?


队列以一种先入先出(FIFO)的线性表,还有一种先入后出的线性表(FILO)叫做栈。
教科书上有明确的定义与描述。类似于现实中排队时的队列(队尾进,队头出),队列只在线性表两端进行操作,插入元素的一端称为表尾,删除(取出)元素的一端称为表头。分别对应于 入队和出队操作。


存储结构


对应于线性存储结构,称为顺序队列,链式存储结构称为链队。实现分别用数组和链表。

顺序队列的实现非常简单。
#include<iostream>
#include<stdlib.h>
#define MaxSize 100
using namespace std;
typedef struct
{
	int data[MaxSize];
	int front,rear; //队首、队尾 指针 
}SqQueue; //队列中两端都会发生变化,所以用头尾指针表示两端的变化 
void InitQueue(SqQueue *&q); 
void DestroyQueue(SqQueue *&q);
bool QueueEmpty(SqQueue *q);
bool InQueue(SqQueue *&q,int e); //入队insert 
bool DeQueue(SqQueue *&q,int &e);//出队delete 

int main(void)
{
	system("pause");
	return 0;
}
void InitQueue(SqQueue *&q)
{
	q = (SqQueue *)malloc(sizeof(SqQueue));
	q->front = q->rear = -1;
}
void DestroyQueue(SqQueue *&q)
{
	free(q);
	q = NULL;
} 
bool QueueEmpty(SqQueue *q)
{
	return q->front == q->rear;//为空 
} 
bool InQueue(SqQueue *&q,int e)
{
	if(q->rear == MaxSize-1)//队满上溢出 
	//环形队列--------if((p->rear+1)%MaxSize == front)
		return false;
	q->rear ++;
	//环形队列--------q->rear = (q->rear+1)%MaxSize;
	q->data[q->rear] = e;
	return true; 
} 
bool DeQueue(SqQueue *&q,int &e)
{
	if(q->front == q->rear)//队空下溢出 
		return false;
	q->front ++;
	//环形队列---------p->front = (p->front+1)%MaxSize; 
	e = q->data[q->front];
	return true;
}
而线性队列在使用中会出现假溢出。即判断队列已满,但实际上并非所有位置都存放了元素。可以通过每次出队后将队列中所有元素前移一个位置解决,但这样会造成很高的额外时间消耗。采用环形队列可以解决这一问题。

环形队列


环形队列即将数组的头和尾连接起来构成环形。为了使队满的条件不与队空的条件(front == rear)冲突。可以舍弃一个元素的存储空间,队头front指向队头的上一个位置,队尾rear指向队尾元素。这样队满条件变为(rear+1)%MaxSize == front.

增加一个判满函数如下:
bool QueueFull(SqQueue *q)
{
	if((q->rear+1)%MaxSize == q->front || (q->front == -1 && q->rear == MaxSize-1))
	{
		return true;
	}
	return false;
}
插入函数修改为:
bool InQueue(SqQueue *&q,int e)
{
	if(QueueFull(q))
		return false;
	q->rear = (q->rear+1)%MaxSize;
	q->data[q->rear] = e;
	return true;
}
删除函数修改为:
bool DeQueue(SqQueue *&q,int &e)
{
	if(QueueEmpty(q))
		return false;
	q->front = (q->front+1)%MaxSize;
	e = q->data[q->front];
}
遍历函数为:
void TraverseQueue(SqQueue *q)//从表头到表尾 
{
	for(int i=0;i<QueueLength(q);i++)
	{
		cout << q->data[(i+q->front+1)%MaxSize] << endl;
	}
	cout << endl;
} 


其余函数均与顺序队列一致。
环形队列在C++中采用类实现如下:
#include<iostream>
#include<stdlib.h>
using namespace std;

/*******************实现环形队列*************/
class MyQueue
{
public:	
	MyQueue(int queueCapacity);//创建队列
	virtual ~MyQueue();//销毁队列 
	void ClearQueue();//清空队列 
	bool QueueEmpty() const;//判断队列是否为空 
	bool QueueFull() const;//判满队列 
	int QueueLength() const;//队列长度 
	bool InQueue(int element);//新元素入队
	bool DeQueue(int &element);//首元素出队
	void QueueTraverse();//遍历队列 
		
private:
	int *m_pQueue;	//队列数组指针
	int m_iQueueLen;	//队列元素个数
	int m_iQueueCapacity;	//队列数组容量 
	int m_iHead;//队头,实质是数组下标 
	int m_iTail; //队尾 
};
//构造函数,创建队列 
MyQueue::MyQueue(int queueCapacity)
{
	m_iQueueCapacity = queueCapacity;
	m_iHead = 0;
	m_iTail = 0;
	m_iQueueLen = 0;//ClearQueue();
	m_pQueue = new int[m_iQueueCapacity];
} 
// 析构函数,销毁队列
MyQueue::~MyQueue()
{
	delete []m_pQueue;
	m_pQueue = NULL;
} 
//清空队列
void MyQueue::ClearQueue()
{
	m_iHead = 0 ;
	m_iTail = 0 ;
	m_iQueueLen = 0;
} 
//判空队列
bool MyQueue::QueueEmpty() const
{
	return m_iQueueLen == 0;
	//m_iQueueLen == 0 ? true : false; 
}
//判满 
bool MyQueue::QueueFull() const
{
	if(m_iQueueCapacity == m_iQueueLen)
	{
		return true;
	}
	else
	{
		return false;
	}
}

//获取队列长度
int MyQueue::QueueLength() const
{
	return m_iQueueLen;
} 
//新元素入队 
bool MyQueue::InQueue(int element)
{
	if(QueueFull())
	{
		return false;
	}
	else
	{
		m_pQueue[m_iTail] = element;
		m_iTail ++;
		m_iTail %= m_iQueueCapacity;
		m_iQueueLen ++;
		return true;
	}
}
//首元素出队
bool MyQueue::DeQueue(int &element) 
{
	if(QueueEmpty())
	{
		return false;
	}
	else
	{
		element = m_pQueue[m_iHead];
		m_iHead ++ ;
		m_iHead %= m_iQueueCapacity;
		m_iQueueLen --;
		return true;
	}
}
//遍历队列
void MyQueue::QueueTraverse()
{
	for(int i=m_iHead; i < m_iHead + m_iQueueLen; i++)
	{
		cout << m_pQueue[i%m_iQueueCapacity] << endl;
	}
} 


int main(void)
{
	//检测一下环形队列是否写正确了 
	MyQueue *p = new MyQueue(4);
	
	p->InQueue(10);
	p->InQueue(20);
	p->InQueue(23);
	p->InQueue(78);
	p->QueueTraverse();
	
	int e = 0;
	p->DeQueue(e);
	cout << endl;
	cout << e << endl;
	cout << endl; 
	p->QueueTraverse();
	
	p->ClearQueue();
	if(p->QueueEmpty())
	{
		cout << "The queue is empty!" << endl;
	}
	
	p->InQueue(238);
	p->InQueue(34);
	p->QueueTraverse();
	cout << "The length of the queue is " << p->QueueLength() << endl;
	p->InQueue(100);
	p->InQueue(299);
	if(p->QueueFull())
	{
		cout << "The queue is full!" << endl; 
	}
	p->QueueTraverse();
	p->~MyQueue();
	cout << "You have destroyed your queue successfully!" << endl; 
	system("pause");
	return 0;
}

如果数据元素是多个数据项组成,在C语言中可采用结构体将多个数据项封装在节点中,C++中可以用类将锁哥数据元素封装为一个数据对象。如果在不同应用场景下数据元素数据类型(封装类型或原有类型)不同,可以采用类模板设计实现代码重用。读者可以自行完成。


链队


在队列的链式存储结构中,可用不含头节点的链表表示。定义队列包含两个节点,其中一个为front指针指向表头,另一个为rear指针指向表尾。链队不存在满队的情况。
实现如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
//链队 
//用不含头节点的链表实现 
//数据节点定义 
typedef struct qnode
{
	int data;
	qnode *next; 
}QNode;
//链队定义 
typedef struct Queue
{
	QNode *front;
	QNode *rear;
}LiQueue;

void InitQueue(LiQueue *&q)//初始化 
{
	q = (LiQueue *)malloc(sizeof(Queue));
	q->front = NULL;
	q->rear = NULL;
}
void DestroyQueue(LiQueue *&q)//销毁 
{
	QNode *p = q->front,*r;
	while(p != NULL)
	{
		r = p;
		p = p->next;
		free(r);
	}
	free(q);
}
bool QueueEmpty(LiQueue *q)
{
	return NULL == q->rear;
}
void InQueue(LiQueue *&q,int e)//入队不会失败 
{
	QNode *p = (QNode *)malloc(sizeof(QNode));
	p->data = e;
	p->next = NULL;
	if(QueueEmpty(q))
	{
		q->front = p;
		q->rear = p;
	}
	else
	{
		q->rear->next = p;
		q->rear = p;
	}
}

bool DeQueue(LiQueue *&q,int &e)//出队 
{
	if(QueueEmpty(q))//队为空 
	{
		return false;
	}
	QNode *t = q->front;
	if(q->front == q->rear)//队中只含一个数据元素 
	{
		q->front = q->rear = NULL;
	}
	else//队中含有两个及以上数据元素 
	{
		q->front = t->next;
	}
	e = t->data;
	free(t);
	t = NULL;
	return true; 
}
bool TraverseQueue(LiQueue *q)
{
	if(QueueEmpty(q))
	{
		return false;
	}
	QNode *p = q->front;
	while(p != NULL)
	{
		cout << p->data << endl;
		p = p->next;
	}
	cout << endl;
	return true;
}

int main(void)
{
	LiQueue *q;
	InitQueue(q);
	InQueue(q,1);
	InQueue(q,2);
	InQueue(q,3);
	InQueue(q,4);
	TraverseQueue(q);
	
	int elem = 0;
	DeQueue(q,elem);
	cout << "The element you deleted is :" << elem << endl;
	TraverseQueue(q);
	
	DeQueue(q,elem);
	DeQueue(q,elem);
	DeQueue(q,elem);
	if(QueueEmpty(q))
	{
		cout << "The List Queue is empty!" << endl;
	}
	TraverseQueue(q);
	DestroyQueue(q);
	system("pause");
	return 0;
}


典型应用


在具体的程序设计中,只要涉及到先进先出的设计,即采用了队列的思想。
队列的一个典型应用就是求解——迷宫问题

迷宫问题是指:给定给定一个M×N的迷宫图、入口与出口、行走规则。求一条从指定入口到出口的路径。
所求路径必须是简单路径,即路径不重复。

迷宫问题可以用栈或者队列来求解。其中使用队列求解出的路径是最短路径
迷宫采用二维数组来表示,其中路用0表示,墙用1表示。为了求解问题的方便,通常在数组的周围加上围墙,即在周围加上两行和两列。形成M+2行,N+2列的迷宫数组。

求解思路使用顺序队列(使用顺序队列的原因是:出队入队操作并不会删除结点,只是改变了队首队尾指针的值,最终还要通过队列中已出队节点来回溯得到路径),队列中的数据元素类型为格点坐标(i,j)和路径中上一格点在队列中的位置pre的封装。pre的设置是为了找到终点后由终点通过pre回溯到起点从而逆序打印出路径(采用递归实现)。在将一个能走的格点入队后,循环搜索它周围的四个格点,并将其中能走的入队,所以必须制定四个方向的搜索顺序(最后若有多条最短路径,则打印出哪一条由搜索顺序决定)。由于路径不重复,所以在在入队后将一个迷宫格点的值赋为-1,避免重复搜索。整体思路类似于广度优先搜索。

实现如下:
由于队列操作简单,其中并没有定义出出队,入队等函数。
注意最后打印出的坐标为(行号,列号),并不是惯用的横纵坐标。

#include<iostream>
#include<stdlib.h>
using namespace std;
const int MaxSize = 100;
typedef struct
{
	int i,j;//迷宫块坐标 
	int pre;//当前路径中前一方块在队列中的位置 
	
}Box;
typedef struct Queue
{
	Box data[MaxSize];
	int rear,front;//front指向当前队头的前一元素,rear指向队尾 
}SqQueue;

//全局数组maze表示迷宫
const int M=4,N=4;
int maze[M+2][N+2] = { 	{1, 1, 1, 1, 1, 1}, //迷宫示例 
						{1, 0, 0, 0, 1, 1}, 
						{1, 0, 1, 0, 0, 1}, 
						{1, 0, 0, 0, 1, 1}, 
						{1, 1, 0, 0, 0, 1}, 
						{1, 1, 1, 1, 1, 1}  };

bool MazePath(int xi,int yi,int xe,int ye);
void print(SqQueue q,int n);

int main(void)
{
	if(MazePath(1,1,4,4))
	{
		cout << "有路径,如上~" << endl; 
	}
	else
	{
		cout << "没有路径~" << endl; 
	}
	
	system("pause");
	return 0;
}


//求迷宫路径算法,xi,yi入口坐标,xe,ye出口坐标 
bool MazePath(int xi,int yi,int xe,int ye)//x表行号,y表列号 
//搜索路径(xi,yi)->(xe,ye)
{
	int i,j;
	bool find = false;//找到出口置1 
	SqQueue qu;//在栈中分配内存 
	qu.rear = qu.front = -1;
	qu.rear ++;
	qu.data[qu.rear].i = xi;
	qu.data[qu.rear].j = yi;//(xi,yi)入队
	qu.data[qu.rear].pre = -1;//表示在队列中没有位于它之前的元素,作为搜索路径时的结束条件
	maze[xi][yi] = -1;//将0置为-1,避免重复搜索
	while(qu.front != qu.rear && !find)//当队列不空且没有找到出口时循环
	{
		qu.front ++;
		i = qu.data[qu.front].i;
		j = qu.data[qu.front].j;//i表行,j表列 
		if(i==xe && j==ye)
		{
			find = true;
			print(qu,qu.front);//打印路径,从当前格点(终点)开始追溯递归打印路径 
			return true;//找到出口 
		}
		//将(i,j)周围四个格点中为路且没有走过的格点进队 
		for(int di=0;di<4;di++)//di表示查找方向,0->3顺时针旋转,分别为上右下左 
		{
			switch(di)
			{
			case 0: i=qu.data[qu.front].i-1;
					j=qu.data[qu.front].j;
					break; 
			case 1: i=qu.data[qu.front].i;
					j=qu.data[qu.front].j+1;
					break;
			case 2: i=qu.data[qu.front].i+1;
					j=qu.data[qu.front].j;
					break;
			case 3: i=qu.data[qu.front].i;
					j=qu.data[qu.front].j-1;
					break;
			}
			if(maze[i][j] == 0)
			{
				qu.rear ++;
				qu.data[qu.rear].i = i;
				qu.data[qu.rear].j = j;
				qu.data[qu.rear].pre = qu.front;//上一个出队元素在队列中的标号 
				maze[i][j] = -1;
			}
		} 
	}
	return false;//未找到路径返回false 
}

//递归打印路径 
void print(SqQueue q,int n)
{
	if(q.data[n].pre == -1)
	{
		cout << "(" << q.data[n].i << "," << q.data[n].j << ")" << endl;
		return;//return 必须写 
	}
	print(q,q.data[n].pre);
	cout << "(" << q.data[n].i << "," << q.data[n].j << ")" << endl;
}



队列的应用非常广泛,比如在图的广度优先遍历中。作为一种最简单的数据结构,限制性的线性表,当然用线性表也可以实现队列的所有功能,但正是由于栈和队列太常用,才单独抽象成一种数据结构。后续会有关于栈的文章。
  • 10
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值