链式队列

链式队列是用链表实现的队列,需要同时维护队头指针和队尾指针。为了操作方便,通常附设一个头结点。

存储结构

typedef struct LinkNode {
    int data;
    struct LinkNode *next;
} LinkNode;

typedef struct {
    LinkNode *front;  // 队头指针
    LinkNode *rear;   // 队尾指针
} LinkQueue;

基本操作实现

初始化

void InitQueue(LinkQueue &Q) {
    Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));  // 头结点
    Q.front->next = NULL;
}

判空

bool QueueEmpty(LinkQueue Q) {
    return Q.front == Q.rear;
}

入队(尾插法)

bool EnQueue(LinkQueue &Q, int x) {
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
    Q.rear->next = s;  // 新结点插入到队尾
    Q.rear = s;         // 更新队尾指针
    return true;
}

出队

bool DeQueue(LinkQueue &Q, int &x) {
    if (QueueEmpty(Q)) return false;
    LinkNode *p = Q.front->next;  // p指向队头元素结点
    x = p->data;
    Q.front->next = p->next;      // 头结点指向新的队头
    if (Q.rear == p) {             // 如果删除的是最后一个元素
        Q.rear = Q.front;          // 更新队尾指针指向头结点
    }
    free(p);
    return true;
}

时间复杂度:所有操作均为 O(1)O(1)

习题

习题 1

最适合用作链队的链表是( )

A. 带队头指针和队尾指针的循环单链表 B. 带队头指针和队尾指针的非循环单链表 C. 只带队头指针的循环单链表 D. 只带队头指针的非循环单链表

答案与解析

答案:B

解析: 队列需要在队尾插入(入队)和队头删除(出队)。

  • 有队头指针可以在O(1)时间内完成出队
  • 有队尾指针可以在O(1)时间内完成入队
  • 非循环单链表即可满足需求,不需要循环

所以最适合的是带队头指针和队尾指针的非循环单链表。

习题 2

简述链式队列的出队操作需要注意什么问题?为什么?

答案与解析

链式队列出队操作需要注意的问题: 当删除的是队列中的最后一个元素时,需要同时更新队尾指针rear,使其指向头结点。

原因: 链式队列通常带有头结点,front指向头结点,rear指向队尾元素结点。

  • 当队列中有多个元素时,出队操作只需要修改头结点的next指针,rear指针不受影响
  • 当队列中只有一个元素时,这个元素既是队头也是队尾。删除这个元素后:
    • 头结点的next变为NULL(队空)
    • 但rear仍然指向已被删除的结点,成为悬空指针
    • 如果不更新rear,后续的入队操作会通过rear->next插入新结点,但rear指向的内存已经被释放,导致内存错误

因此,出队时需要判断被删除的结点是否是rear指向的结点(即是否是最后一个元素),如果是,则将rear更新为指向头结点(与front相同),保证队空时front和rear都指向头结点。