链栈的表示和实现
链栈是一种基于链表实现的栈(Stack),它遵循后进先出(LIFO)原则。与数组栈相比,链栈的优势在于动态内存分配,避免了固定大小限制,适合处理不确定规模的数据。下面,我将从概念、数据结构表示、基本操作实现等方面逐步讲解,使用C语言代码示例,语言自然易懂。
1. 链栈的基本概念
栈是一种线性数据结构,只允许在栈顶(Top)进行插入和删除操作。链栈使用单链表来存储元素:
- 栈顶:链表的头节点(Head)作为栈顶,操作高效。
- 节点结构:每个节点包含数据域和指针域,指针指向下一个节点。
- 优势:
- 动态扩展:无需预先分配大小,内存按需分配。
- 时间复杂度:入栈(Push)和出栈(Pop)操作均为$O(1)$,因为只涉及栈顶节点。
- 空间复杂度:$O(n)$,其中$n$是元素个数,每个节点占用额外指针空间。
2. 链栈的数据结构表示
在C语言中,我们定义节点结构体(Node)和栈结构体(Stack)。链栈通常只需一个指针指向栈顶节点,不需要额外存储容量信息。
// 定义节点结构体
typedef struct Node {
int data; // 存储数据,假设为整数类型
struct Node* next; // 指向下一个节点的指针
} Node;
// 定义栈结构体
typedef struct Stack {
Node* top; // 栈顶指针,指向链表头部
} Stack;
- 解释:
Node表示链表节点,包含数据data和指向下一个节点的指针next。Stack只包含一个指针top,指向栈顶节点。当栈为空时,top为NULL。
3. 基本操作实现
链栈的核心操作包括初始化、入栈、出栈、查看栈顶、判断栈空和销毁栈。我将逐一实现这些操作,并提供C语言代码。所有操作时间复杂度均为$O(1)$(除了销毁栈为$O(n)$),因为只涉及栈顶指针操作。
(1) 初始化栈
创建一个空栈,将栈顶指针设为NULL。
// 初始化栈
Stack* initStack() {
Stack* stack = (Stack*)malloc(sizeof(Stack)); // 分配栈内存
if (stack == NULL) {
printf("内存分配失败!\n");
exit(1); // 异常处理
}
stack->top = NULL; // 栈顶指针初始化为空
return stack;
}
- 说明:使用
malloc动态分配栈结构体内存,并将top设为NULL表示空栈。
(2) 入栈操作(Push)
在栈顶插入新元素。创建新节点,将其插入链表头部,并更新栈顶指针。
// 入栈操作
void push(Stack* stack, int data) {
Node* newNode = (Node*)malloc(sizeof(Node)); // 创建新节点
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1);
}
newNode->data = data; // 设置节点数据
newNode->next = stack->top; // 新节点指向原栈顶
stack->top = newNode; // 更新栈顶指针为新节点
}
- 示例:调用
push(stack, 10)后,新节点成为栈顶,原栈顶成为其下一个节点。
(3) 出栈操作(Pop)
删除栈顶元素并返回其值。需要检查栈是否为空。
// 出栈操作
int pop(Stack* stack) {
if (isEmpty(stack)) { // 检查栈空
printf("栈为空,无法出栈!\n");
exit(1);
}
Node* temp = stack->top; // 临时保存栈顶节点
int data = temp->data; // 获取栈顶数据
stack->top = temp->next; // 更新栈顶为下一个节点
free(temp); // 释放原栈顶内存
return data; // 返回删除的数据
}
- 说明:先保存栈顶数据,然后移动栈顶指针并释放节点内存。如果栈空,报错退出。
(4) 查看栈顶元素(Peek)
返回栈顶元素值,但不删除节点。
// 查看栈顶元素
int peek(Stack* stack) {
if (isEmpty(stack)) {
printf("栈为空,无栈顶元素!\n");
exit(1);
}
return stack->top->data; // 直接返回栈顶数据
}
(5) 判断栈是否为空
检查栈顶指针是否为NULL。
// 判断栈空
int isEmpty(Stack* stack) {
return (stack->top == NULL); // 栈顶为NULL时返回1(真),否则0(假)
}
(6) 销毁栈
释放所有节点内存,防止内存泄漏。需要遍历链表释放每个节点。
// 销毁栈
void destroyStack(Stack* stack) {
Node* current = stack->top;
while (current != NULL) {
Node* temp = current; // 临时保存当前节点
current = current->next; // 移动到下一个节点
free(temp); // 释放当前节点
}
free(stack); // 释放栈结构体内存
}
- 时间复杂度:$O(n)$,因为需要遍历所有节点释放内存。
4. 完整示例代码
以下是一个简单的主函数示例,演示链栈的使用:
#include <stdio.h>
#include <stdlib.h>
// 节点和栈定义(同上)
int main() {
Stack* stack = initStack(); // 初始化栈
push(stack, 10); // 入栈元素10
push(stack, 20); // 入栈元素20
push(stack, 30); // 入栈元素30
printf("栈顶元素: %d\n", peek(stack)); // 输出30
printf("出栈元素: %d\n", pop(stack)); // 输出30
printf("出栈元素: %d\n", pop(stack)); // 输出20
if (isEmpty(stack)) {
printf("栈为空\n");
} else {
printf("栈非空,当前栈顶: %d\n", peek(stack)); // 输出10
}
destroyStack(stack); // 销毁栈
return 0;
}
- 运行结果:输出栈顶元素、出栈元素和栈状态。例如,先入栈10,20,30,然后出栈两次,最后栈顶为10。
5. 总结
链栈是一种高效、灵活的栈实现方式:
- 优点:动态内存管理,避免溢出;操作简单高效。
- 缺点:每个节点额外指针占用空间;内存分配/释放可能引入开销。
- 适用场景:数据规模未知或频繁变化的场景,如函数调用栈、表达式求值。 通过以上内容,您应该能掌握链栈的核心原理和C语言实现。在实际应用中,确保正确处理内存管理以避免泄漏。如果您有疑问,欢迎继续讨论!
1170

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



