链式队列
链式队列是用链表实现的队列,需要同时维护队头指针和队尾指针。为了操作方便,通常附设一个头结点。
存储结构
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;
}
链式队列出队时,需要特别注意删除的是最后一个元素的情况!此时队尾指针rear也需要更新,指向头结点,否则rear会成为悬空指针。
时间复杂度:所有操作均为 。
习题
习题 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都指向头结点。
