队列的顺序存储与链式存储(C语言版)
- 一、队列的顺序存储(初始化、判断队空、入队、出队、获取队头元素、获取当前队列的元素个数)
- 二、带头结点队列的链式存储(初始化、判断队空、入队、出队、获取队头元素、获取当前队列的元素个数)
- 三、链式存储的问题答疑
-
-
-
- [1. 为什么 `s` 必须是指针类型?](#1. 为什么
s
必须是指针类型?) - [2. `->` 和 `.` 的区别](#2.
->
和.
的区别) - [3. 获取队列元素个数](#3. 获取队列元素个数)
- [4. 初始化函数解析](#4. 初始化函数解析)
- [1. 为什么 `s` 必须是指针类型?](#1. 为什么
-
-
一、队列的顺序存储(初始化、判断队空、入队、出队、获取队头元素、获取当前队列的元素个数)
cpp
#include <stdio.h>
using namespace std;
/*
循环队列:
队空:Q.front == Q.rear
队满:(Q.rear + 1) % MaxSize == Q.front
队伍长度:(Q.rear + MaxSize - Q.front) % MaxSize
队头指针+1:(Q.front+1) % MaxSize
队尾指针+1:(Q.rear+1) % MaxSize
*/
#define MaxSize 50
typedef struct{
int data[MaxSize]; // 队列
int front, rear; // 队头指针、队尾指针
}SqQueue;
//InitQueue(&Q):初始化队列,构造一个空队列Q。
void InitQueue(SqQueue &Q){
Q.front = Q.rear = 0;
}
//QueueEmpty(Q):判队列空,若队列为空返回true,否则返回false。
bool QueueEmpty(SqQueue Q){
if(Q.front == Q.rear) return true;
return false;
}
//EnQueue (&Q,x):入队,若队列Q未满,将x加入,使之成为新的队尾。
bool EnQueue(SqQueue &Q, int x) {
if((Q.rear + 1) % MaxSize == Q.front) return false;
Q.data[Q.rear] = x;
Q.rear = (Q.rear + 1) % MaxSize;
return true;
}
//DeQueue(&Q,&x):出队,若队列Q非空,删除队首元素,并用x返回。
bool DeQueue(SqQueue &Q, int &x){
if(Q.front == Q.rear) return false;
x = Q.data[Q.front];
Q.front = (Q.front + 1) % MaxSize;
return true;
}
//GetHead (Q,&x):读队首元素,若队列Q非空,则将队首元素赋值给x。
bool GetHead(SqQueue Q, int &x) {
if(Q.front == Q.rear) return false;
x = Q.data[Q.front];
return true;
}
// GetLength(Q,&l): 获取队列长度
int GetLength(SqQueue Q){
return (Q.rear + MaxSize - Q.front) % MaxSize;
}
int main() {
bool flag = true;
SqQueue Q;
InitQueue(Q);
while(flag) {
printf("\n===============================\n");
printf("1.入队\n");
printf("2.出队\n");
printf("3.判队列空\n");
printf("4.读队首元素\n");
printf("5.获取队列长度\n");
printf("6.结束\n");
printf("===============================\n");
printf("选择:");
int choose;
scanf("%d", &choose);
switch(choose){
case 1:
printf("输入新入队的数:");
int num;
scanf("%d", &num);
if(EnQueue(Q, num)) printf("%d入队成功!\n", num);
else printf("入队失败!\n");
break;
case 2:
int deNum;
if(DeQueue(Q, deNum)) printf("此时出队的曾队头元素是:%d\n", deNum);
else printf("出队失败!\n");
break;
case 3:
if(QueueEmpty(Q)) printf("此时队列为空!\n");
else printf("此时队列仍然不为空!\n");
break;
case 4:
int geNum;
if(GetHead(Q, geNum)) printf("此时的队首元素为%d\n", geNum);
else printf("获取失败!\n");
break;
case 5:
printf("此时的队列长度为: %d\n", GetLength(Q));
break;
case 6:
flag = false;
}
}
}
二、带头结点队列的链式存储(初始化、判断队空、入队、出队、获取队头元素、获取当前队列的元素个数)
c
#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
typedef struct LinkNode{ // 队列结点
int data; // 数据
struct LinkNode *next; // 指针
}LinkNode;
typedef struct{
LinkNode *front, *rear; // 头指针、尾指针
}LinkQueue;
//InitQueue(&Q): 初始化队列,构造一个空队列Q。
void InitQueue(LinkQueue &Q){
// front 和 rear 都指向这个头结点,注意:头结点不存储有效数据(仅作为哨兵节点)
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode)); // malloc() 返回的是内存地址(指针),不是结构体实例本身,所以是(LinkNode*)
Q.front->next = NULL;
}
//QueueEmpty(Q): 判队列空,若队列Q为空返回true,否则返回false。
bool QueueEmpty(LinkQueue Q){
if(Q.front == Q.rear) return true;
return false;
}
//EnQueue (&Q,x): 入队,将x加入,使之成为新的队尾。
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;
}
//DeQueue(&Q, &x): 出队,若队列Q非空,删除队首元素,并用x返回。
bool DeQueue(LinkQueue &Q, int &x){
if(Q.front == Q.rear) return false;
LinkNode *p = Q.front->next; // 临时存储队头结点(头结点的下一位)
x = p->data;
Q.front->next = p->next;
if(Q.rear == p) {
Q.front = Q.rear;
}
free(p);
return true;
}
//GetHead (Q,&x):读队首元素,若队列Q非空,则将队首元素赋值给x。
bool GetHead(LinkQueue Q, int &x){
if(Q.front == Q.rear) return false;
x = Q.front->next->data; // 指针访问成员必须用 ->,不能用 .
return true;
}
// 获取当前队列的元素个数
int GetLength(LinkQueue Q){
int length = 0;
LinkNode *p = Q.front->next;
while(p != NULL){
length++;
p = p->next;
}
return length;
}
int main() {
LinkQueue Q;
InitQueue(Q); // 初始化队列
bool flag = true;
while(flag) {
printf("\n===============================\n");
printf("1.入队\n");
printf("2.出队\n");
printf("3.判队列空\n");
printf("4.读队首元素\n");
printf("5.获取队列当前元素个数\n");
printf("6.结束\n");
printf("===============================\n");
printf("选择:");
int choose;
scanf("%d", &choose);
switch(choose){
case 1:
int x1;
printf("输入要入队的值:");
scanf("%d", &x1);
if(EnQueue(Q, x1)) printf("%d成功入队!\n", x1);
else printf("入队失败!\n");
break;
case 2:
int x2;
if(DeQueue(Q, x2)) printf("%d成功出队!\n", x2);
else printf("出队失败!\n");
break;
case 3:
if(QueueEmpty(Q)) printf("队列为空!\n");
else printf("此时队列仍不为空!\n");
break;
case 4:
int x3;
if(GetHead(Q, x3)) printf("此时队头元素是:%d\n", x3);
else printf("获取失败!\n");
break;
case 5:
int len;
len = GetLength(Q);
printf("队列的当前元素个数是:%d\n", len);
break;
case 6:
flag = false;
break;
default:
printf("非法输入!\n");
break;
}
}
}
三、链式存储的问题答疑
1. 为什么 s
必须是指针类型?
c
LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode)); // 正确
// LinkNode s = (LinkNode)malloc(sizeof(LinkNode)); // 错误
- 内存分配原理 :
malloc()
返回的是内存地址(指针),不是结构体实例本身 - 动态内存管理 :
- 队列需要动态创建/销毁节点
- 指针允许我们操作堆(heap)内存中的对象
- 结构体变量是栈(stack)对象,函数结束时会自动销毁
- 类型匹配 :
malloc
返回void*
需要强制转换为LinkNode*
- 不能将指针赋值给结构体变量(类型不匹配)
2. ->
和 .
的区别
操作符 | 适用对象 | 示例 | 等效写法 |
---|---|---|---|
. |
结构体实例 | node.data |
直接访问成员 |
-> |
结构体指针 | ptr->data |
(*ptr).data |
错误分析:
c
x = Q.front->next->data; // 正确(两级指针访问)
x = Q.front->next.data; // 错误
Q.front
是指针 → 必须用->
Q.front->next
返回的是指针(LinkNode*
),不是结构体实例- 指针访问成员必须用
->
,不能用.
3. 获取队列元素个数
实现方案:
c
int QueueLength(LinkQueue Q) {
int count = 0;
LinkNode *p = Q.front->next; // 跳过头结点
while(p != NULL) {
count++;
p = p->next;
}
return count;
}
优化建议(推荐):
c
typedef struct {
LinkNode *front, *rear;
int length; // 新增长度计数器
} LinkQueue;
// 初始化时设置 length=0
void InitQueue(LinkQueue &Q) {
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
Q.front->next = NULL;
Q.length = 0; // 初始化长度
}
// 入队操作增加
bool EnQueue(LinkQueue &Q, int x) {
// ...原有代码...
Q.length++; // 增加长度计数
return true;
}
// 出队操作减少
bool DeQueue(LinkQueue &Q, int &x) {
// ...原有代码...
Q.length--; // 减少长度计数
return true;
}
4. 初始化函数解析
c
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
- 创建头结点 :在堆内存分配一个
LinkNode
结构体 - 指针赋值 :
front
和rear
都指向这个头结点- 头结点不存储有效数据(仅作为哨兵节点)
- 队列状态 :
front->next = NULL
→ 空队列标志front == rear
→ 队列为空