本节实验课总体安排:
1、顺序栈的基本操作
2、链队列的基本操作
根据栈和队列的运算规则,选取不同的存储方式。
一、顺序栈的入栈、出栈、取栈顶元素、遍历栈
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define STACK_INIT_SIZE 100
#define STACK_ADD_SIZE 10
typedef int SElemType;
typedef struct sqstack {
SElemType* top;
SElemType* base;
int max_size;
} Sqstack;
// 初始化栈
bool init_stack(Sqstack *sq)
{
sq->base = (SElemType*)malloc(STACK_INIT_SIZE * sizeof(SElemType));
if (!sq->base) {
printf("初始化栈失败\n");
return false;
}
sq->top = sq->base;
sq->max_size = STACK_INIT_SIZE;
return true;
}
// 判栈满
bool judge_full(Sqstack sq)
{
return (sq.top - sq.base >= sq.max_size);
}
// 扩容
bool growth_stack(Sqstack *sq)
{
int current_size = sq->top - sq->base;
SElemType* new_base = (SElemType*)realloc(sq->base, (sq->max_size + STACK_ADD_SIZE) * sizeof(SElemType));
if (!new_base) {
printf("扩容失败\n");
return false;
}
sq->base = new_base;
sq->top = sq->base + current_size;
sq->max_size += STACK_ADD_SIZE;
return true;
}
// 判栈空
bool judge_empty(Sqstack sq)
{
return (sq.base == sq.top);
}
// 遍历输出
bool print_stack(Sqstack sq)
{
if (judge_empty(sq)) {
printf("当前栈为空\n");
return false;
}
printf("栈中的元素为:");
int i = sq.top - sq.base, j;
for (j = 1; j <= i; j++)
printf("%d ", *(sq.top - j));
printf("\n");
return true;
}
// 入栈
bool push(Sqstack *sq, SElemType data)
{
if (judge_full(*sq)) {
printf("栈满,自动扩容\n");
if (!growth_stack(sq))
return false;
}
*(sq->top) = data;
sq->top++;
return true;
}
// 出栈
bool pop(Sqstack *sq, SElemType *data)
{
if (judge_empty(*sq)) {
printf("当前栈为空\n");
return false;
}
*data = *(--sq->top);
return true;
}
// 取栈顶元素
bool get_top(Sqstack sq, SElemType *data)
{
if (judge_empty(sq)) {
printf("当前栈为空\n");
return false;
}
*data = *(sq.top - 1);
return true;
}
int main()
{
printf("顺序栈操作\n");
Sqstack sq;
init_stack(&sq);
int i, n;
SElemType data;
printf("请输入入栈元素个数:");
scanf("%d", &n);
for (i = 0; i < n; i++) {
printf("请输入第%d个元素:", i + 1);
scanf("%d", &data);
push(&sq, data);
}
print_stack(sq);
printf("出栈的栈顶元素:");
if (pop(&sq, &data))
printf("%d\n", data);
print_stack(sq);
printf("输出栈顶元素:");
if (get_top(sq, &data))
printf("%d\n", data);
free(sq.base); // 释放内存
return 0;
}
二、代码讲解
数据结构说明(Sqstack)
typedef struct sqstack {
SElemType* top; // 指向栈顶下一个可用位置(即“栈顶指针”的惯用表示)
SElemType* base; // 指向栈底第一个元素的地址
int max_size; // 当前分配的最大元素个数(容量)
} Sqstack;
-
base指向已经分配的数组起始地址(数组下标 0)。 -
top指向数组中下一次入栈的位置(当栈为空时top == base)。 -
当前栈中元素个数为
top - base(指针相减,单位是元素个数)。 -
空间布局示例(入栈 10,20,30 后):
base -> [10][20][30][ ][ ] ...
^ ^
base top(指向第4格)
函数:init_stack(Sqstack *sq)
作用:初始化顺序栈,分配初始内存并设置 base、top、max_size。
实现步骤:
-
用
malloc(STACK_INIT_SIZE * sizeof(SElemType))为sq->base分配STACK_INIT_SIZE个元素的内存。 -
判断
malloc返回值是否为NULL:若是NULL,表示分配失败,打印错误并返回false。 -
若分配成功,令
sq->top = sq->base(表示空栈),并设置sq->max_size = STACK_INIT_SIZE。 -
返回
true表示初始化成功。
时间复杂度:O(1)。
注意点:
-
malloc失败时应妥善处理(当前实现打印错误并返回 false,调用者应检查返回值)。 -
推荐使用
size_t表示容量以避免与int的类型问题(当前用int,在元素非常多时可能溢出)。
函数:judge_full(Sqstack sq)
作用:判断栈是否已满(即当前元素个数是否达到或超过容量)。
实现步骤:
-
计算
sq.top - sq.base(返回当前元素个数)。 -
与
sq.max_size比较:若>=,返回true,否则false。
时间复杂度:O(1)。
注意点:
-
该函数以值传递
Sqstack(拷贝结构体);对小结构体没问题,但若结构体变大可改为指针传参以避免拷贝开销。 -
top - base的结果类型是ptrdiff_t(算术上比int更合适),但用在比较时通常可行。
函数:growth_stack(Sqstack *sq)
作用:为栈扩容(把容量增加 STACK_ADD_SIZE),并保持已有元素不变。
实现步骤:
-
先计算当前元素个数
current_size = sq->top - sq->base(这是必要的,因为realloc可能移动内存,必须用current_size在完成后重置top)。 -
调用
realloc(sq->base, (sq->max_size + STACK_ADD_SIZE) * sizeof(SElemType))请求扩大分配空间。-
realloc若返回非NULL,会返回新的内存地址(可能与原地址相同或不同),并保留原数据(按字节复制)。 -
若
realloc返回NULL,原来sq->base的内存仍然有效(注意不要把realloc的返回直接赋回原指针,除非先检测返回值)。
-
-
判断
realloc返回值new_base:-
若为
NULL,打印“扩容失败”,返回false(原内存仍然可用)。 -
若非
NULL,把sq->base = new_base,根据current_size更新sq->top = sq->base + current_size,并把sq->max_size += STACK_ADD_SIZE。
-
-
返回
true。
时间复杂度:O(n)(realloc 复制当前数据到新内存,n = 元素个数)。
注意点 / 风险:
-
realloc可能失败,正确的做法是先把返回结果保存在临时指针new_base,只有在非 NULL 时才覆盖sq->base(你的实现是这样的,安全)。 -
扩容策略为固定增量
STACK_ADD_SIZE,在大量 push 的情况下会频繁realloc。一个更常用的策略是按倍数扩容(例如max_size *= 2),能把均摊时间复杂度保持为常数。 -
current_size必须先计算并在realloc后重新计算top,否则如果realloc把内存移动到新地址,原top指针会成为悬空指针。
函数:judge_empty(Sqstack sq)
作用:判断栈是否为空。
实现步骤:
-
比较
sq.base == sq.top:若相等则为空栈,返回true;否则返回false。
时间复杂度:O(1)。
注意点:
-
同样以值拷贝方式传参,若想避免拷贝可以传指针。
函数:print_stack(Sqstack sq)
作用:按从栈顶到栈底顺序打印栈中当前所有元素(典型的“从栈顶开始打印”以展示 LIFO 顺序)。
实现步骤:
-
调用
judge_empty(sq)判断是否为空,若空则打印"当前栈为空"并返回false。 -
计算元素个数
i = sq.top - sq.base。 -
用
for (j = 1; j <= i; j++)依次输出*(sq.top - j),这样第一次输出的是*(sq.top - 1)(栈顶元素),依次向下到*(sq.base)(栈底元素)。 -
输出换行并返回
true。
时间复杂度:O(n),n = 当前元素个数(必须遍历所有元素以打印)。
注意点:
-
输出顺序是从栈顶到栈底(符合观察栈内容的直觉)。
-
函数以值传参(拷贝结构体),但没有修改栈,所以不会影响外部
sq。
函数:push(Sqstack *sq, SElemType data)
作用:将一个元素压入栈顶(入栈)。
实现步骤:
-
调用
judge_full(*sq)(注意传值)检查是否已满。如果已满:-
打印
"栈满,自动扩容"。 -
调用
growth_stack(sq)做扩容;若扩容失败则返回false(无法入栈)。
-
-
*(sq->top) = data;将元素写入top指向的位置(此位置为下一个可用槽)。 -
sq->top++;将top指针向后移动一个元素位置,表示新的栈顶下一个可用位置。 -
返回
true表示入栈成功。
时间复杂度:均摊 O(1)。(通常一次写入 O(1),只有在扩容时为 O(n))
注意点:
-
如果
growth_stack失败,push返回false。调用处应检查返回值以避免数据丢失(你的main中未检查返回值,建议补充)。 -
*(sq->top) = data;在top已指向有效内存时安全;如果top已越界(说明judge_full有逻辑错误),会导致写越界。
函数:pop(Sqstack *sq, SElemType *data)
作用:弹出栈顶元素(出栈),并把弹出的值写入 *data。
实现步骤:
-
调用
judge_empty(*sq)判断是否为空栈;若为空打印"当前栈为空"并返回false。 -
使用前置递减
--sq->top,然后*data = *(sq->top);。前置递减把top指回最后已占用元素的位置,随后读出该元素值。 -
返回
true表示出栈成功,返回的*data就是被弹出的元素。
时间复杂度:O(1)。
注意点:
-
出栈后没有清除原内存(不是必要),但如果元素类型较大或含有资源(例如指针需要释放),应在必要时做清理。
-
pop没有做缩容(通常不需要,但可优化以节省内存)。
函数:get_top(Sqstack sq, SElemType *data)
作用:读取当前栈顶(但不弹出),把栈顶元素存入 *data。
实现步骤:
-
检查是否空栈(
judge_empty(sq));若空则打印提示并返回false。 -
*data = *(sq.top - 1);:因为sq.top指向下一个空位置,所以栈顶元素位于sq.top - 1。 -
返回
true。
时间复杂度:O(1)。
注意点:
-
传参用值拷贝
Sqstack sq,函数内部不修改栈,所以这样是安全的。 -
必须保证
sq.top != sq.base(即非空)才能安全地做sq.top - 1。
函数:main()
作用:演示并驱动栈的使用流程:初始化、批量入栈、打印、出栈、读栈顶、释放内存(free)。
实现步骤(按代码流程):
-
打印启动信息
printf("顺序栈操作\n");。 -
声明
Sqstack sq;并调用init_stack(&sq)初始化栈空间(但注意当前main没有检查返回值,建议检查以处理分配失败)。 -
读取要入栈元素的个数
n(用scanf("%d", &n))。注意:应检查scanf的返回值以保证合法输入。 -
循环
for (i = 0; i < n; i++):-
读取每个元素
scanf("%d", &data)(同样应检查返回值)。 -
调用
push(&sq, data)入栈(应检查push的返回值以处理扩容失败)。
-
-
调用
print_stack(sq)打印当前栈内容。 -
调用
pop(&sq, &data)弹出一个元素并打印;若pop成功则打印弹出的值。 -
再次
print_stack(sq)显示出栈后的栈内容。 -
调用
get_top(sq, &data)获取当前栈顶并打印(若栈非空)。 -
free(sq.base)释放之前malloc分配的内存,避免内存泄露。 -
return 0;
时间复杂度(整体):取决于用户输入量 n,总体 O(n)(入栈 n 次 + 打印一次 O(n))。
注意点:
-
主函数中对
init_stack、push、pop、scanf等返回值没有做充分检查,实际代码应健壮化处理错误分支(内存不足、输入错误、扩容失败等)。 -
free(sq.base)在程序结束时释放内存,这是必要的良好实践(尽管程序退出时 OS 会回收,但在大型程序中应显式释放)。
小示例(手工演示一次入栈过程)
假设 STACK_INIT_SIZE = 5,初始 base = 0x1000(伪地址),top = base:
-
入栈
10:写*(top)=10,然后top++。现在top - base = 1。 -
入栈
20:写*(top)=20,top++,现在top - base = 2。
若继续入栈直到top - base == max_size,push会触发growth_stack扩容(realloc分配新空间并把前current_size项拷贝过来,更新base和top)。
三、链队列的初始化、入队、出队、遍历队列、回文数
-
// 链队列 (C语言版) #include <stdio.h> #include <stdlib.h> #include <stdbool.h> typedef char ElemType; // 队列结点 typedef struct qnode { ElemType data; struct qnode *next; } QueueNode, *QNode; // 链队列结构 typedef struct linkqueue { QNode front; // 队头指针 QNode rear; // 队尾指针 } LinkQueue; // 函数声明 bool init_queue(LinkQueue *q); // 初始化 bool en_queue(LinkQueue *q, ElemType data); // 入队列 bool de_queue(LinkQueue *q, ElemType *data); // 出队列 bool clear_queue(LinkQueue *q); // 清空队列 bool judge_empty(LinkQueue q); // 判空 bool get_length(LinkQueue q, int *length); // 队列长度 bool get_head(LinkQueue q, ElemType *data); // 得到队头元素 bool traverse_queue(LinkQueue q); // 遍历队列 void show(); void switch_channel(int channel, LinkQueue *q); int main() { LinkQueue q; int channel; do { show(); scanf("%d", &channel); switch_channel(channel, &q); printf("\n"); } while (1); return 0; } // 菜单 void show() { printf("链队列操作\n"); printf("1--初始化、入队元素\n"); printf("2--出队列\n"); printf("3--输出队头元素\n"); printf("4--队列长度\n"); printf("5--清空队列\n"); printf("6--遍历队列\n"); printf("0--退出\n"); } // 菜单分发 void switch_channel(int channel, LinkQueue *q) { ElemType data; int n, i; switch (channel) { case 1: init_queue(q); printf("请输入要入队列元素的数目:"); scanf("%d", &n); for (i = 0; i < n; i++) { printf("请输入第%d个元素:", i + 1); scanf(" %c", &data); // 注意前面有空格,避免回车干扰 en_queue(q, data); } traverse_queue(*q); break; case 2: if (de_queue(q, &data)) printf("出队列元素为:%c\n", data); traverse_queue(*q); break; case 3: if (get_head(*q, &data)) printf("队头元素为:%c\n", data); traverse_queue(*q); break; case 4: get_length(*q, &n); printf("当前队列长度为:%d\n", n); break; case 5: clear_queue(q); traverse_queue(*q); break; case 6: traverse_queue(*q); break; case 0: exit(0); default: printf("无效选择\n"); break; } } // 初始化 bool init_queue(LinkQueue *q) { q->front = q->rear = (QNode)malloc(sizeof(QueueNode)); if (!q->front) return false; q->front->next = NULL; printf("初始化完成\n"); return true; } // 入队列 bool en_queue(LinkQueue *q, ElemType data) { QNode p = (QNode)malloc(sizeof(QueueNode)); if (!p) return false; p->data = data; p->next = NULL; q->rear->next = p; q->rear = p; return true; } // 出队列 bool de_queue(LinkQueue *q, ElemType *data) { if (judge_empty(*q)) { printf("队列为空\n"); return false; } QNode p = q->front->next; *data = p->data; q->front->next = p->next; if (q->rear == p) // 若删除的是最后一个结点 q->rear = q->front; free(p); return true; } // 清空队列 bool clear_queue(LinkQueue *q) { QNode p; while (q->front->next) { p = q->front->next; q->front->next = p->next; free(p); } q->rear = q->front; printf("清空完成\n"); return true; } // 判空 bool judge_empty(LinkQueue q) { return (q.front == q.rear); } // 队列长度 bool get_length(LinkQueue q, int *length) { *length = 0; QNode p = q.front->next; while (p) { (*length)++; p = p->next; } return true; } // 得到队头元素 bool get_head(LinkQueue q, ElemType *data) { if (judge_empty(q)) { printf("队列为空\n"); return false; } *data = q.front->next->data; return true; } // 遍历队列 bool traverse_queue(LinkQueue q) { if (judge_empty(q)) { printf("遍历队列--队列为空\n"); return false; } printf("遍历队列:"); QNode p = q.front->next; while (p) { printf("%c ", p->data); p = p->next; } printf("\n"); return true; }

被折叠的 条评论
为什么被折叠?



