专栏目录(数据结构与算法解析):https://blog.youkuaiyun.com/qq_40344524/article/details/107785323
队列
上节对栈进行了介绍,今天我们来介绍另一个特殊的线性数据结构——队列,队列只能在一端插入数据,另一端删除数据,它和栈一样,也是一种操作受限制的线性表,进行插入操作的称为队尾,进行删除操作的称为队头,队列中的数据被称为元素,没有任何元素的队列称为空队列。
由于只能一端删除或者插入,所以只有最先进入队列的才能被删除,因此队列是先进先出结构的。
具体见下方操作演示图:
通过上述图片可以发现,队列形似一条管道,通常只有一个进水口和一个出水口,因此最先进入队列的元素自然而然会先出队列。
队列同样可通过数组和链表两种方式进行实现,接下来就通过代码对队列的操作进行详细说明:
数组队列
初始化队列
//队列结构体,声明一个可存放10个元素的队列
typedef int ElemType;
typedef struct QNode
{
ElemType data[10];
int Head;
int Tail;
} Queue;
//初始化数组队列,队头和队尾都指向起始位置
Queue InitQueue(Queue *Q)
{
Q->Head= 0;
Q->Tail= 0;
}
入队
void PushQueue(Queue *Q,ElemType e)
{
//判断队满的情况
if((Q->Tail+1)%10 == Q->Head)
{
return ;
}
Q->data[Q->Tail] = e;
Q->Tail= (Q->Tail+1)%10;
}
出队
void PopQueue(Queue *Q,ElemType *e)
{
//判断队空的情况
if(Q->Head== Q->Tail)
{
return ;
}
*e = Q->data[Q->Head];
Q->Head = (Q->Head+1)%10;
}
判空
bool IsEmptyQueue(Queue Q)
{
if(Q.Head == Q.Tail)
{
return TRUE;
}
else
{
return FALSE;
}
}
取队头元素
//取对头元素
ElemType QueueFront(Queue* Q) {
if(Q.Head == Q.Tail)
{
return 0;
}
return Q.data[Q.Head];
}
取元素个数
int SizeQueue(Queue *Q)
{
return Q.Head-Q.Tail;
}
链式队列
初始化队列
//链式队列结点结构
typedef int DataType;
typedef struct QNode
{
struct QNode* Next;
DataType data;
}QNode;
//链式队列结构体
typedef struct Queue
{
QNode* front; // 指向队头元素
QNode* rear; // 指向队尾元素
int size;//队列元素个数
}Queue;
//队列的初始化
void QueueInit(Queue* Q) {
if (Q == NULL) {
return;
}
Q->_front = NULL;
Q->rear = NULL;
Q->size = 0;
}
入队
//入队列
void QueuePush(Queue* Q, DataType data) {
if (Q == NULL) {
return;
}
QNode* newNode = (QNode*)malloc(sizeof(QNode));
newNode->data = data;
newNode->Next = NULL;
if(Q->front==NULL&&Q->rear==NULL) {
//入队第一个元素
Q->front = newNode;
Q->rear = newNode;
}
else{
//入队后续元素,插入元素,队尾指针后移
Q->rear->Next = newNode;
Q->rear = newNode;
}
Q->size++;
}
出队
//出队列
void QueuePop(Queue* Q) {
if (Q == NULL) {
return;
}
if (Q->front == NULL) {//队列为空
return;
}
//保存第一个元素
QNode* pFront = Q->front;
//把队列的头往后移动
Q->front = Q->front->Next;
//释放已经出队列的结点
free(pFront );
Q->size--;
}
判空
//判断队列是否为空
bool IsQueueEmpty(Queue* Q) {
if (Q == NULL) {
return true;
}
if (Q->front == NULL) {
return true;
}
return false;
}
取队头元素
//返回对头元素
DataType QueueFront(Queue* Q) {
if (Q == NULL) {
return 0;
}
if (Q->front == NULL) {//队列为空
return 0;
}
return Q->front->data;
}
取元素个数
//返回队列有效元素个数
int QueueSize(Queue* Q) {
if (Q == NULL) {
return 0;
}
return Q->size;
}
循环队列
在上述介绍中我们可以发现在数组队列中,当队尾指针已经到数组的上界,不能再有入队操作,但其实数组中还有空位置,这就叫做“假溢出”,为了解决这个问题引入了一种特殊的队列——循环队列,循环队列可用如下图例进行展示:
通过以上图示我们可以发现循环队列就是将数组队列的队头和队尾相接形成一个闭环,使得我们可以在队列中循环的做入队出队操作,从而有效的避免“假溢出”现象,另外通过上图能够发现当队满和对空时头尾指针都指向同一个位置,那么我们如何判断是队满还是对空呢?这里我们就需要重点使用计数变量了,当队满时计数变量是循环队列的空间大小,对空时技术队列为0,接下来通过代码对循环队列进行实现:
初始化队列
//队列结构体
typedef int DataType;
typedef struct {
DataType* base;
int front; //队头
int rear; //队尾
}Queue;
//初始化队列
void InitQueue(Queue *Q) {
Q->base = (DataType*)malloc(6 * sizeof(DataType)); //声明可存放6个元素的空间
if (Q->base == NULL)
return;
Q->front = 0;
Q->rear = 0;
}
入队
bool PushQueue(Queue *q, DataType e) {
if ((q.rear + 1) % 6 == q.front) //队满,无法继续入队
return ERROR;
q->base[q->rear] = e;
q->rear = (q->rear + 1) % 6;
return OK;
}
出队
bool PopQueue(Queue *q) {
if(q.rear == q.front) //队空,无法出队
return ERROR;
q->front = (q->front + 1) % 6;
return OK;
}
判空
bool IsEmpty(Queue q) {
return q.rear == q.front;
}
取队头元素
DataType frontQueue(Queue q) {
return q.base[q.front];
}
取元素个数
int QueueSize(Queue q) {
return (q.rear - q.front + 6) % 6;
}
总结
通过对上面内容的学习可以知道队列是受到特殊约束的,它的特点是先进先出(FIFO),队列在生活中的应用比栈要广泛一些,另外在操作系统中也有很多地方用到队列的操作:CPU 进程的调度、内存的分配、磁盘的管理等通常都需要用到队列。
队列就讨论到这里,下一节开始讲解较为复杂的数据结构——散列表。
以上是我的一些粗浅的见解,有表述不当的地方欢迎指正,谢谢!
表述能力有限,部分内容讲解的不到位,有需要可评论或私信,看到必回…