数据结构-栈
一、了解栈
栈是一种线性数据结构,遵循“后进先出”(Last In, First Out,LIFO)的原则。也就是说,最后被压入栈中的元素最先被弹出。栈可以看作是一个只允许在一端进行插入和删除操作的集合。
栈的基本特性:
-
操作限制:
- 压栈(Push):将元素添加到栈顶。
- 弹栈(Pop):从栈顶移除元素并返回该元素。
- 查看栈顶元素(Peek/Top):返回栈顶元素但不移除它。
-
栈的状态:
- 空栈:没有任何元素。
- 满栈:达到栈的最大容量(在使用数组实现时)。
-
应用场景:
- 函数调用(调用栈)
- 表达式求值(如后缀表达式)
- 括号匹配
- 深度优先搜索(DFS)
总结
栈是一种简单而重要的数据结构,广泛应用于计算机科学和编程中,尤其是在管理函数调用、表达式求值和实现算法时。由于其特有的LIFO特性,栈能够有效地处理需要临时存储和后续访问的元素。
二、栈的顺序存储结构(C语言)
1. 栈的顺序存储结构
// 栈的顺序存储结构
#define MaxSize 50 // 定义一个常量 MaxSize,表示栈的最大容量为 50// 定义一个顺序栈的结构体 SqStack
typedef struct {int data[MaxSize]; // 用一个数组 data 来存储栈中的元素,大小为 MaxSizeint top; // top 用于记录栈顶元素的索引,初始值通常为 -1 表示栈空
} SqStack; // SqStack 结构体类型的定义结束
2. 栈的初始化
// 初始化栈
void InitStack(SqStack* S) {S->top = -1; // 将栈顶指针初始化为 -1,表示栈为空
}
3. 判断栈为空
// 判断栈是否为空
bool StackEmpty(SqStack* S) {// 检查栈顶指针是否为 -1if (S->top == -1)return true; // 如果是,返回 true,表示栈为空elsereturn false; // 否则返回 false,表示栈不为空
}
4. 进栈
// 进栈操作
bool Push(SqStack* S, int e) {// 检查栈是否已满if (S->top == MaxSize - 1)return false; // 如果满了,返回 falseS->top++; // 栈顶指针增加S->data[S->top] = e; // 将元素 e 压入栈顶return true; // 返回 true,表示成功压入元素
}
5. 出栈
// 出栈操作
bool Pop(SqStack* S, int* e) {// 检查栈是否为空if (S->top == -1)return false; // 如果栈为空,返回 false*e = S->data[S->top]; // 将栈顶元素赋值给 eS->top--; // 栈顶指针减少,移除栈顶元素return true; // 返回 true,表示成功出栈
}
6. 读栈顶元素
// 读顶栈元素
bool GetTop(SqStack* S, int* e) {// 检查栈是否为空if (S->top == -1)return false; // 如果栈为空,返回 false*e = S->data[S->top]; // 将栈顶元素的值赋给 ereturn true; // 返回 true,表示成功获取栈顶元素
}
7. 销毁栈
// 销毁栈
void DestroyStack(SqStack* S) {S->top = -1; // 将栈顶指针设置为 -1,表示栈已被销毁
}
三、栈的链式存储结构(C语言)
1. 栈的链式存储结构
// 链表节点结构体定义
typedef struct LNode {int data; // 节点存储的数据struct LNode* next; // 指向下一个节点的指针
} LNode, * Linknode; // Linknode 是指向 LNode 的指针类型
2. 栈的初始化
// 初始化链表(栈)-- 带头结点
bool InitStack(Linknode* LS) {// 为链表头节点分配内存(*LS) = (LNode*)malloc(sizeof(LNode));// 检查内存分配是否成功if ((*LS) == NULL) return false; // 如果分配失败,返回 false(*LS)->next = NULL; // 初始化头节点的 next 指针为 NULLreturn true; // 返回 true,表示初始化成功
}
3. 判断栈为空
// 判断栈是否为空 -- 带头结点
bool StackEmpty(Linknode LS) {// 检查头结点是否为空// if (LS == NULL) return false; // 此行代码被注释掉,表示不考虑头结点为空的情况// 如果头结点的下一个节点为空,则栈为空if (LS->next == NULL) return true;elsereturn false; // 否则栈不为空
}
4. 进栈
// 进栈
bool Push(Linknode* LS, int e) {// 检查头结点是否为空,若为空则无法进栈if (*LS == NULL) return false;// 创建一个新的节点LNode* p;p = (LNode*)malloc(sizeof(LNode)); // 为新节点分配内存// 检查内存分配是否成功if (p == NULL) return false;// 将新节点的下一个指针指向当前栈顶元素p->next = (*LS)->next;// 更新头结点的下一个指针,使其指向新节点(*LS)->next = p;// 将新节点的数据域赋值为进栈的元素p->data = e;return true; // 返回成功进栈的标志
}
5. 出栈
// 出栈
bool Pop(Linknode* LS, int* e) {// 检查栈是否为空,如果栈指针为空,返回falseif ((*LS) == NULL) return false;// 检查栈是否只有一个元素(即栈顶元素),如果是,返回falseif ((*LS)->next == NULL) return false;// 获取栈顶元素的指针LNode* p = (*LS)->next;// 将栈顶指针指向下一个元素(*LS)->next = p->next;// 将栈顶元素的值赋给e*e = p->data;// 释放栈顶元素的内存free(p);// 返回true,表示成功出栈return true;
}
6. 读栈顶元素
// 读栈顶元素
bool GetTop(Linknode* S, int* e) {// 检查栈是否为空,如果栈指针为空,返回falseif ((*S) == NULL) return false;// 检查栈是否只有一个元素(即栈顶元素),如果是,返回falseif ((*S)->next == NULL) return false;// 将栈顶元素的值赋给e*e = (*S)->next->data;// 返回true,表示成功获取栈顶元素return true;
}
7. 销毁栈
// 销毁栈
void DestroyStack(Linknode* S) {// 检查栈是否为空,如果栈指针为空,直接返回if ((*S) == NULL) return;LNode* p;// 循环释放栈中的所有元素while ((*S)->next != NULL) {p = (*S)->next; // 获取当前栈顶元素的指针(*S)->next = p->next; // 将栈顶指针指向下一个元素free(p); // 释放当前栈顶元素的内存}// 释放栈的头节点free(*S);// 将指针重置为 NULL,避免悬空指针*S = NULL;
}
四、总代码(C语言)
1. 顺序栈
#include <stdio.h>
#include <stdbool.h>// 栈的顺序存储结构
#define MaxSize 50 // 定义一个常量 MaxSize,表示栈的最大容量为 50// 定义一个顺序栈的结构体 SqStack
typedef struct {int data[MaxSize]; // 用一个数组 data 来存储栈中的元素,大小为 MaxSizeint top; // top 用于记录栈顶元素的索引,初始值通常为 -1 表示栈空
} SqStack; // SqStack 结构体类型的定义结束// 初始化栈
void InitStack(SqStack* S) {S->top = -1; // 将栈顶指针初始化为 -1,表示栈为空
}// 判断栈是否为空
bool StackEmpty(SqStack* S) {// 检查栈顶指针是否为 -1if (S->top == -1)return true; // 如果是,返回 true,表示栈为空elsereturn false; // 否则返回 false,表示栈不为空
}// 进栈操作
bool Push(SqStack* S, int e) {// 检查栈是否已满if (S->top == MaxSize - 1)return false; // 如果满了,返回 falseS->top++; // 栈顶指针增加S->data[S->top] = e; // 将元素 e 压入栈顶return true; // 返回 true,表示成功压入元素
}// 出栈操作
bool Pop(SqStack* S, int* e) {// 检查栈是否为空if (S->top == -1)return false; // 如果栈为空,返回 false*e = S->data[S->top]; // 将栈顶元素赋值给 eS->top--; // 栈顶指针减少,移除栈顶元素return true; // 返回 true,表示成功出栈
}// 读顶栈元素
bool GetTop(SqStack* S, int* e) {// 检查栈是否为空if (S->top == -1)return false; // 如果栈为空,返回 false*e = S->data[S->top]; // 将栈顶元素的值赋给 ereturn true; // 返回 true,表示成功获取栈顶元素
}// 销毁栈
void DestroyStack(SqStack* S) {S->top = -1; // 将栈顶指针设置为 -1,表示栈已被销毁
}// int main() {return 0;
}
2. 链栈
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>// 链表节点结构体定义
typedef struct LNode {int data; // 节点存储的数据struct LNode* next; // 指向下一个节点的指针
} LNode, * Linknode; // Linknode 是指向 LNode 的指针类型// 初始化链表(栈)-- 带头结点
bool InitStack(Linknode* LS) {// 为链表头节点分配内存(*LS) = (LNode*)malloc(sizeof(LNode));// 检查内存分配是否成功if ((*LS) == NULL) return false; // 如果分配失败,返回 false(*LS)->next = NULL; // 初始化头节点的 next 指针为 NULLreturn true; // 返回 true,表示初始化成功
}// 判断栈是否为空 -- 带头结点
bool StackEmpty(Linknode LS) {// 检查头结点是否为空// if (LS == NULL) return false; // 此行代码被注释掉,表示不考虑头结点为空的情况// 如果头结点的下一个节点为空,则栈为空if (LS->next == NULL) return true;elsereturn false; // 否则栈不为空
}// 进栈
bool Push(Linknode* LS, int e) {// 检查头结点是否为空,若为空则无法进栈if (*LS == NULL) return false;// 创建一个新的节点LNode* p;p = (LNode*)malloc(sizeof(LNode)); // 为新节点分配内存// 检查内存分配是否成功if (p == NULL) return false;// 将新节点的下一个指针指向当前栈顶元素p->next = (*LS)->next;// 更新头结点的下一个指针,使其指向新节点(*LS)->next = p;// 将新节点的数据域赋值为进栈的元素p->data = e;return true; // 返回成功进栈的标志
}// 出栈
bool Pop(Linknode* LS, int* e) {// 检查栈是否为空,如果栈指针为空,返回falseif ((*LS) == NULL) return false;// 检查栈是否只有一个元素(即栈顶元素),如果是,返回falseif ((*LS)->next == NULL) return false;// 获取栈顶元素的指针LNode* p = (*LS)->next;// 将栈顶指针指向下一个元素(*LS)->next = p->next;// 将栈顶元素的值赋给e*e = p->data;// 释放栈顶元素的内存free(p);// 返回true,表示成功出栈return true;
}// 读栈顶元素
bool GetTop(Linknode* S, int* e) {// 检查栈是否为空,如果栈指针为空,返回falseif ((*S) == NULL) return false;// 检查栈是否只有一个元素(即栈顶元素),如果是,返回falseif ((*S)->next == NULL) return false;// 将栈顶元素的值赋给e*e = (*S)->next->data;// 返回true,表示成功获取栈顶元素return true;
}// 销毁栈
void DestroyStack(Linknode* S) {// 检查栈是否为空,如果栈指针为空,直接返回if ((*S) == NULL) return;LNode* p;// 循环释放栈中的所有元素while ((*S)->next != NULL) {p = (*S)->next; // 获取当前栈顶元素的指针(*S)->next = p->next; // 将栈顶指针指向下一个元素free(p); // 释放当前栈顶元素的内存}// 释放栈的头节点free(*S);// 将指针重置为 NULL,避免悬空指针*S = NULL;
}// 示例用法
int main() {Linknode stack;if (InitStack(&stack)) {printf("Stack initialized successfully.\n");}else {printf("Failed to initialize stack.\n");}// 记得释放内存free(stack);return 0;
}
五、总结
栈是一种重要的线性数据结构,遵循“后进先出”(LIFO)的原则,具有压栈、弹栈和查看栈顶元素等基本操作。栈的应用广泛,包括函数调用的管理、表达式求值、括号匹配和深度优先搜索等。栈可以通过顺序存储结构(如数组)或链式存储结构(如链表)实现。顺序栈通过一个固定大小的数组来存储元素,使用一个索引记录栈顶位置,具有简单高效的特性,但容易出现栈满的情况。链式栈则通过动态分配内存的链表节点来实现,能够灵活地使用内存,避免栈满的情况,但在访问和管理节点时相对复杂。栈的基本操作包括初始化、判断是否为空、进栈、出栈、获取栈顶元素和销毁栈等。通过这些操作,栈能够有效地管理临时数据,支持多种算法和应用场景。总体而言,栈是计算机科学中不可或缺的基础数据结构之一。
