1.队列
队列,和栈一样,也是一种对数据的"存"和"取"有严格要求的线性存储结构。
与栈结构不同的是,队列的两端都"开口",要求数据只能从一端进,从另一端出,如下图所示:
队列存储结构的实现有以下两种方式:
顺序队列:在顺序表的基础上实现的队列结构;
链队列:在链表的基础上实现的队列结构;
2.顺序队列
顺序队列,即采用顺序表模拟实现的队列结构。
2.1简单实现
由于顺序队列的底层使用的是数组,因此需预先申请一块足够大的内存空间初始化顺序队列。除此之外,为了满足顺序队列中数据从队尾进,队头出且先进先出的要求,我们还需要定义两个指针(front 和 rear)分别用于指向顺序队列中的队头元素和队尾元素. rear一般指向下一个队尾元素的存放位置。
(a)当队列为空时,rear与front位置重合。 (b)元素从队尾入队时,rear递增 (c)元素从队头出队是,fron递增 (d)c5 c6入队后,c3 c4出队时,rear已经已经不能在增长了,front签的空间也不能再利用了。 这样实现是有问题存在的, 所以我们需要换一种思路。
2.2循环队列
我们可以这样想象,把队列的收尾相连,这样出队空出的空间,还可以在队尾继续追加元素时利用。
(a)为初始时的空队列 front == rear (b)abc入队 (c)a出队 (d1)可以看到对满时 front==rear, 那么该如何判断是对满还是对空呢? (d2)牺牲掉数组中的一个存储空间,判断数组满员的条件是:尾指针的下一个位置和头指针相遇,就说明数组满了。(Q.rear + 1) % MAXSIZE == Q.front 下面我们就以循环队列的实现方式,实现一些队列的基本操作
队列定义
typedef int Status;
typedef int QElemType; /* SElemType类型根据实际情况而定,这里假设为int */
typedef struct {
QElemType data[MAXSIZE];
int front;//头指针
int rear;//尾指针 若队列不空 指向队尾元素的下一个位置
} SqQueue;
2.2.1初始化
Status InitQueue(SqQueue *Q){
Q->front = 0;
Q->rear = 0;
return OK;
}
2.2.2队列清空
//头尾指针都置为0
Status ClearQueue(SqQueue *Q){
Q->front = Q->rear = 0;
return 0;
}
2.2.3队列是否为空
Status QueueEmpty(SqQueue Q){
//若队头指针等于队尾指针 则为空队列
return Q.front == Q.rear;
}
2.2.4队列的当前长度
int QueueLength(SqQueue Q){
return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
2.2.5获取队列的头元素
//若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR;
Status GetHead(SqQueue Q,QElemType *e){
if (QueueEmpty(Q)) {
return ERROR;
}
*e = Q.data[Q.front];
return OK;
}
2.2.6入队列
//若队列未满,则插入元素e为新队尾元素
Status EnQueue(SqQueue *Q,QElemType e){
//队列已满
if ((Q->rear+1)%MAXSIZE == Q->front) {
return ERROR;
}
//将元素e赋值给队尾
Q->data[Q->rear] = e;
//rear指针向后移动一位,若到最后则转到数组头部;
Q->rear = (Q->rear+1)%MAXSIZE;
return OK;
}
2.2.7出队列
//若队列不空,则删除Q中队头的元素,用e返回值
Status DeQueue(SqQueue *Q,QElemType *e){
//判断队列是否为空
if (QueueEmpty(*Q)) {
return ERROR;
}
//将队头元素赋值给e
*e = Q->data[Q->front];
//front 指针向后移动一位,若到最后则转到数组头部
Q->front = (Q->front+1+MAXSIZE)%MAXSIZE;
return OK;
}
2.2.8遍历队列
//从队头到队尾依次对队列的每个元素数组
Status QueueTraverse(SqQueue Q){
int i = Q.front;
while (i!=Q.rear) {
printf("%d ", Q.data[i]);
i = (i+1)%MAXSIZE;
}
return OK;
}
3.链式队列
链式队列,简称"链队列",即使用链表实现的队列存储结构。
链式队列的实现思想同顺序队列类似,只需创建两个指针(命名为 front 和 rear)分别指向链表中队列的队头元素和队尾元素.
链式队列结构定义如下
typedef int Status;
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
typedef struct QNode /* 结点结构 */
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct /* 队列的链表结构 */
{
QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;
3.1 初始化队列
Status InitQueue(LinkQueue *Q){
//1. 头/尾指针都指向新生成的结点
Q->front = Q->rear = malloc(sizeof(QueueNode));
//2.判断是否创建新结点成功与否
if (!Q->front) {
return ERROR;
}
//3.头结点的指针域置空
Q->front->next = NULL;
return OK;
}
3.2销毁队列Q
Status DestoryQueue(LinkQueue *Q){
//遍历整个队列,销毁队列的每个结点
while (Q->front) {
QueuePtr p = Q->front->next;
free(Q->front);
Q->front = p;
}
return OK;
}
3.3将队列Q置空
Status ClearQueue(LinkQueue *Q){
QueuePtr p, q;
p = Q->front->next;
Q->front->next = NULL;
Q->rear = Q->front;
while (p) {
q = p->next;
free(p);
p = q;
}
return OK;
}
3.4判断队列Q是否为空
Status QueueEmpty(LinkQueue Q){
return Q.front == Q.rear;
}
3.5 获取队列长度
int QueueLength(LinkQueue Q){
int len = 0;
QueuePtr p = Q.front;
while (p != Q.rear) {
len++;
p = p->next;
}
return len;
}
3.6 插入元素e为队列Q的新元素
Status EnQueue(LinkQueue *Q,QElemType e){
QueuePtr p = malloc(sizeof(QueueNode));
//判断是否分配成功
if (!p) {
return ERROR;
}
p->data = e;
p->next = NULL;
//将新结点插入到队尾
Q->rear->next = p;
//修改队尾指针
Q->rear = p;
return OK;
}
3.7 出队列
Status DeQueue(LinkQueue *Q,QElemType *e){
//判断队列是否为空;
if (Q->front == Q->rear) {
return ERROR;
}
//将要删除的队头结点暂时存储在p
QueuePtr p = Q->front->next;
//将要删除的队头结点的值赋值给e
*e = p->data;
//将原队列头结点的后继p->next 赋值给头结点后继
Q->front->next = p->next;
//若队头就是队尾,则删除后将rear指向头结点
if(Q->rear == p) {
Q->rear = Q->front;
}
free(p);
return OK;
}
3.8 获取队头元素
Status GetHead(LinkQueue Q,QElemType *e){
//队列非空
if (Q.front != Q.rear) {
//返回队头元素的值,队头指针不变
*e = Q.front->next->data;
return TRUE;
}
return FALSE;
}
3.9遍历队列
Status QueueTraverse(LinkQueue Q){
QueuePtr p;
p = Q.front->next;
while (p) {
printf("%d ",p->data);
p = p->next;
}
printf("\n");
return OK;
}
4.总结
由于顺序表的局限性,我们在顺序队列中实现数据入队和出队的基础上,又对实现代码做了改进,令其能够充分利用数组中的空间。链式队列就不需要考虑空间利用的问题,因为链式队列本身就是实时申请空间。因此,这可以算作是链式队列相比顺序队列的一个优势。