06--队列的链式实现

1,108 阅读6分钟

# 06--队列的顺序实现已经介绍了队列的特征及顺序存储的实现,本文章将从设计队列的链式存储的时现。

一、队列的链式实现设计

队列的特征:

  • 1.线性结构的特征队列都有,关于线性结构的特征可以看文章# 01--线性表的顺序存储;
  • 2.队列的特殊限定性特征是:先进先出
  • 3.队列由一个方向入队一个方向出队。我们称入队的方向为队尾,出队的方向队头

结合队列和特征和链式存储的特性,我们来设计一下队列的链式实现。

二、准备工作

设计一些回调状态和类型重定义

#define OK 1

#define ERROR 0

#define TRUE 1

#define FALSE 0

#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status;

typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

三、节点的设计

这里我们选择单向链表来实现队列的链式实现。节点设计如下:

typedef struct QNode    /* 结点结构 */
{
    QElemType data;
    struct QNode *next;

}QNode,*QueuePtr;

data表示数据域,next是指针域,指向下一结点。

根据单向链表的特征和队列的特征(单向链表的特征可以看# 01--单向链表),我们设计如下结构体作为队列的链式实现:

typedef struct            /* 队列的链表结构 */

{

    QueuePtr front,rear; /* 队头、队尾指针 */

}LinkQueue;

队头为front,队尾为rear。

四、队列操作

队列的操作包括初始化队列、销毁队列、清空队列、队列判空、获取队列长度、入队、出队、获取队头元素和遍历队列。

1.初始化队列

Status InitQueue(LinkQueue *Q){
    //1. 头/尾指针都指向新生成的结点
    Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode));

    //2.判断是否创建新结点成功与否
    if (!Q->front) {
        return ERROR;
    }

    //3.头结点的指针域置空
    Q->front->next = NULL;
    
    return OK;
}
  • 1.设计一个头结点作为队列为空时队头和队尾的统一指向,关于在链表中设计头结点带来的好处可以查看文章# 01--单向链表
  • 2.队列为空时,队头front和队尾rear指向同一位置,即头结点,只有头结点时,它的next指向NULL;
  • 3.队头是出队的位置队尾是入队的位置,它指向头结点,头结点的next指向下一个结点。如下图所示:

未命名文件.png

  • 1.按照如上图设计,当入队时,只需要创建新结点插入rear后面即可;
  • 2.出队时拿到front的next即结点A,再拿到A的next即结点B,释放A结点,再将front的next指向B即可实现出队。

2.销毁队列

Status DestoryQueue(LinkQueue *Q){
    //遍历整个队列,销毁队列的每个结点
    while (Q->front) {
        Q->rear = Q->front->next;
        free(Q->front);
        Q->front = Q->rear;
    }
    return OK;
}
  • 1.销毁队列时,队头front和队尾rear已经失去存在的意义,所以可以把他们当做辅助空间使用;
  • 2.销毁队列时,头结点也没有存在的必要了,所以可以从头结点遍历到最后一个结点,并依次将它们进行释放;
  • 3.front指向头结点,把rear作为辅助空间指向待删除结点的下一个结点,方便下一次删除。

3.清空队列

Status ClearQueue(LinkQueue *Q){
    QueuePtr p,q;
    Q->rear = Q->front;
    p = Q->front->next;
    Q->front->next = NULL;

    while (p) {
        q = p;
        p = p->next;
        free(q);
    }

    return OK;
}
  • 1.清空队列时,头结点需要保留,方便下一次入队;
  • 2.队列为空时,队尾rear和队头front指向同一位置,即头结点;
  • 3.队列为空时,队头的next指向为NULL;
  • 4.拿到队头后面的所有结点,依次遍历并释放。

4.队列判空

Status QueueEmpty(LinkQueue Q){

    if (Q.front == Q.rear)
        return TRUE;
    else
        return FALSE;
}

队列为空时,队头front的队尾rear指向同一位置。

5.获取队列长度

int QueueLength(LinkQueue Q){
    int i= 0;
    QueuePtr p;
    p = Q.front;

    while (Q.rear != p) {
        i++;
        p = p->next;
    }
    return i;
}
  • 1.从队头front的next遍历到队尾,每次累加1,最后即可得到队列的长度;
  • 2.也可以在设计队列的结构体时加入队列的长度length,在操作入队、出队和清空队列等操作时修改length的值。

6.入队

Status EnQueue(LinkQueue *Q,QElemType e){
    //为入队元素分配结点空间,用指针s指向;
    QueuePtr s = (QueuePtr)malloc(sizeof(QNode));

    //判断是否分配成功
    if (!s) {
         return ERROR;
    }

    //将新结点s指定数据域.
    s->data = e;
    s->next = NULL;

    //将新结点插入到队尾
    Q->rear->next = s;

    //修改队尾指针
    Q->rear = s;

    return OK;
}
  • 1.队列的链式实现入队时无需判满
  • 2.创建一个结点,将新结点指入队尾,因为队尾在单向链表的最后一个结点,所以新结点插入队列后成为了新的队尾,所以新结点的next指向NULL;
  • 3.新结点入队后,记得将队尾rear指向新结点

7.出队

Status DeQueue(LinkQueue *Q,QElemType *e){
    QueuePtr p;

    //判断队列是否为空;
    if (Q->front == Q->rear) {
        return ERROR;
    }
    //将要删除的队头结点暂时存储在p
    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;
}
  • 1.出队时,需要判断队列是否为空,为空时不能出队;
  • 2.队头front指向头结点,头结点的next指向队列的第一个有效数据;
  • 3.出队前需要拿到待删除结点和待删除结点的下一个结点,删除待删除结点后,将头结点的next指向删除结点的下一个结点;
  • 4.如果删除的结点是队尾,而需要将队尾rear指向队头front

8.获取队头元素

Status GetHead(LinkQueue Q,QElemType *e){
    //队列非空
    if (Q.front != Q.rear) {
        //返回队头元素的值,队头指针不变
        *e =  Q.front->next->data;
        return TRUE;
    }
    return  FALSE;
}
  • 1.获取队头数据时,需要判空
  • 2.因为队头指向的是头结点,所以获取队头数据应该获取的是front->next的data。

9.遍历队列

Status QueueTraverse(LinkQueue Q){

    QueuePtr p;
    p = Q.front->next;

    while (p) {
        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n");

    return OK;
}

队列的遍历和单向链表的遍历一样,从队头,即头结点的next开始,直到next指向为NULL时结束。

五、总结

队列的链式存储的实现使用了单向链表的数据结构,并巧妙的设计了一个头结点,将队头front指向头结点,方便了出队;将队尾rear指向了单向链表的最后一个结点,方便了入队