赞
踩
数据结构之队列(C语言实现)
在这里将介绍三种队列:数组实现的循环队列、链表队列和具有实用价值的优先级队列。本次只介绍前两种,优先级队列在下一次博客中再单独说明。
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
一、 队列介绍
1. 顺序队列
建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置,如图所示:
每次在队尾插入一个元素,rear增1;每次从队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元,这种现象称为假溢出。
顺序队列中的溢出现象:
(1) "下溢"现象:当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。
(2)"真上溢"现象:当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。
(3)"假上溢"现象:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为"假上溢"现象。
由于普通顺序队列的局限性(假溢出)导致的大量空间被浪费,所以此种队列基本没有什么实用价值,下面介绍实际中使用较多的循环队列。
2.循环队列
在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1或front指针增1 时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。自己真从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。
循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件时front=rear,而队列判满的条件时front=(rear+1)%MaxSize。队空和队满的情况如图:
队空:
队满
使用循环队列虽然可以解决顺序队列中浪费内存的问题,但这里任然存在一个问题,就是对于循环队列而言,队空和队满的条件都是rear==front,导致两种情况无法区分。
为了区分队空和队满,可以采用空一个元素不使用的方法。如下:
此时队空的条件为:front==rear,而队满的条件则为:(rear+1)%MAXSIZE == front,从而加以区分。
下面针对循环队列使用代码进行实现:
//定义结构体以及循环队列的操作
//CQueue.h
#pragma once //这句可以省略,作用是防止头文件被重复包含
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 10
typedef struct _CNode
{
int elem[MAXSIZE];
int front; //下标可取,指向队头元素
int rear; //下标不可取,指向元素应放入的位置
}CNode, *CQueue;
void InitCQueue(CQueue pQueue); //初始化队列
void Push(CQueue pQueue, int val); //入队,从队尾(rear)入
bool Pop(CQueue pQueue, int *rtval); //出队,从队首(front)出
int GetCQueueLen(CQueue pQueue); //获取队列长度
void ShowQueue(CQueue pQueue); //输出队列所有元素
bool IsEmpty(CQueue pQueue); //队列为空则返回true
bool IsFull(CQueue pQueue); //队列满则返回false
bool GetFront(CQueue pQueue, int *rtval); //获取队首元素
//CQueue.cpp
#include"CQueue.h"
int main()
{
CNode head;
InitCQueue(&head);
for (int i = 1; i < 10; ++i)
{
Push(&head,i);
}
printf("输出队列长度:%d\n", GetCQueueLen(&head));
ShowQueue(&head);
int tmp = 0; //用于暂存出队的元素
while (!IsEmpty(&head))
{
if (Pop(&head, &tmp))
{
printf("出队元素为:%d\n", tmp);
}
}
return 0;
}
//初始化队列
voidInitCQueue(CQueue pQueue)
{
if (NULL == pQueue)
return;
pQueue->front = 0;
pQueue->rear = 0;
}
//入队,从队尾(rear)入
void Push(CQueue pQueue, int val)
{
//入队之前应该先判断队列是否已经满了
if (IsFull(pQueue))
{
return;
}
pQueue->elem[pQueue->rear]= val; //将元素放入队列,这里也说明front是可取的
pQueue->rear = (pQueue->rear+ 1) % MAXSIZE; //更新队尾位置
}
//出队,从队首(front)出
bool Pop(CQueue pQueue, int *rtval)
{
//出队之前应该先判断队列是否是空的
if (IsEmpty(pQueue))
{
return false;
}
*rtval = pQueue->elem[pQueue->front]; //取出队头元素
pQueue->front = (pQueue->front+ 1) % MAXSIZE; //更新队头位置
return true;
}
//获取队列长度
intGetCQueueLen(CQueue pQueue)
{
return ((pQueue->rear - pQueue->rear + MAXSIZE) % MAXSIZE);
}
//输出队列所有元素
voidShowQueue(CQueue pQueue)
{
int front = pQueue->front;
int rear = pQueue->rear;
while (front != rear)
{
printf("%5d", pQueue->elem[front++]);
}
printf("\n");
}
//队列为空则返回true
boolIsEmpty(CQueue pQueue)
{
return pQueue->rear == pQueue->front;
}
//队列满则返回false
bool IsFull(CQueue pQueue)
{
return (pQueue->rear + 1)%MAXSIZE == pQueue->front;
}
//获取队首元素
boolGetFront(CQueue pQueue, int *rtval)
{
if (!IsEmpty(pQueue))
{
*rtval = pQueue->elem[pQueue->front]; //将队头元素通过*rtval返回
return true;
}
return false;
}
3.队列链表实现
链队列不需要实现IsFull方法,因为链表的存储不是连续的,只要内存中有空闲内存就可以使用。另外,为了防止内存泄漏,增加了销毁队列操作,用于释放所有从堆中申请的内存。
//LQueue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef struct _LNode
{
int data;
struct _LNode *next;
}LNode;
typedef struct _LQueue
{
LNode *front;
LNode *rear;
}Queue, *LQueue;
void InitCQueue(LQueue pQueue); //初始化队列
LNode* BuyNode(int val); //从堆中申请一个节点的内存空间
void Push(LQueue pQueue, int val); //入队,从队尾(rear)入
bool Pop(LQueue pQueue, int *rtval); //出队,从队首(front)出
int GetCQueueLen(LQueue pQueue); //获取队列长度
void ShowQueue(LQueue pQueue); //输出队列所有元素
bool IsEmpty(LQueue pQueue); //队列为空则返回true
bool GetFront(LQueue pQueue, int *rtval); //获取队首元素
void Destroy(LQueue pQueue); //销毁队列(释放所有节点的内存空间)
//LQueue.cpp
#include"LQueue.h"
int main()
{
Queue head;
InitCQueue(&head);
Push(&head,1);
Push(&head,2);
Push(&head,3);
Push(&head,4);
Push(&head,5);
ShowQueue(&head);
while (!IsEmpty(&head))
{
int tmp;
if (Pop(&head, &tmp))
{
printf("出队元素:%5d\n", tmp);
}
}
Destroy(&head);
return 0;
}
//初始化队列
void InitCQueue(LQueue pQueue)
{
if (NULL == pQueue) //实际上就是初始化头结点
{
return;
}
pQueue->front = NULL;
pQueue->rear = NULL;
}
LNode*BuyNode(int val)
{
LNode *pTmp = (LNode*)malloc(sizeof(LNode));
pTmp->data= val;
pTmp->next= NULL;
return pTmp;
}
//入队,从队尾(rear)入
void Push(LQueue pQueue, int val)
{
LNode *pCur = BuyNode(val);
if (NULL == pQueue->rear)
{
pQueue->front = pCur;
pQueue->rear = pQueue->front;
}
else
{
pQueue->rear->next = pCur;
pQueue->rear = pCur;
}
}
//出队,从队首(front)出
bool Pop(LQueue pQueue, int *rtval)
{
if (!IsEmpty(pQueue))
{
LNode *pTmp = pQueue->front;
*rtval = pTmp->data;
pQueue->front = pTmp->next;
free(pTmp);
if (NULL == pQueue->front)
{
pQueue->rear = pQueue->front;
}
return true;
}
return false;
}
//获取队列长度
intGetCQueueLen(LQueue pQueue)
{
int iCount = 0;
LNode *pCur = pQueue->front;
while (NULL != pCur)
{
++iCount;
pCur= pCur->next;
}
return iCount;
}
//输出队列所有元素
voidShowQueue(LQueue pQueue)
{
LNode *pCur = pQueue->front;
while (NULL != pCur)
{
printf("%5d", pCur->data);
pCur= pCur->next;
}
printf("\n");
}
//队列为空则返回true
boolIsEmpty(LQueue pQueue)
{
return pQueue->front == nullptr;
}
//获取队首元素
boolGetFront(LQueue pQueue, int *rtval)
{
if (IsEmpty(pQueue))
{
return false;
}
*rtval = pQueue->front->data;
return true;
}
//销毁队列(释放所有节点的内存空间)
voidDestroy(LQueue pQueue)
{
LNode *pCur = pQueue->front;
LNode *pTmp;
while (NULL != pCur)
{
pTmp= pCur->next;
free(pCur);
pCur= pTmp;
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。