栈和队列是两种重要的数据结构。从数据结构的角度来看,栈和队列也是线性链表,特殊在于栈和队列的基本操作是线性链表操作的子集
目录
1. 栈
1.1 栈的定义
栈是限定仅在表尾进行插入和删除操作的线性表。对于栈而言,表尾称为栈顶(top),表头端称为栈底(bottom)
栈的修改遵循后进先出的原则。栈的基本操作有:在栈顶进行插入和删除,栈的初始化,判空以及取出栈顶元素。将插入元素称为入栈,取出元素称为出栈(都在顶部进行操作)
1.2 栈的实现
1.2.1 顺序栈的实现
栈的顺序存储结构用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时设指针top指示栈顶元素在顺序栈的位置
栈的初始化操作为:按设定的初始分配量进行第一次存储分配,base可称为栈底指针,在顺序栈中,他始终指向栈底的位置。若base指针为NULL则说明栈结构不存在。称top指针为栈顶指针,其初始值指向栈底,每当插入一个元素top+1,删除元素时top-1
#define STACK_INIT_SIZE 100 //存储空间初始分配量
#define STACKINCREMENT 10 //存储空间分配增量
typedef struct {
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
//---基本操作---
Status InitStack(SqStack &S); //构造一个空栈S
Status DestoryStack(SqStack &S); //销毁栈S
Status ClearStack(SqStack &S); //将S直为空栈
Status StackEmpty(SqStack S); //若栈为空,则返回True否则返回False
int StackLength(SqStack S); //返回S的元素个数
Status GetTop(SqStack S,SElemType &e); //返回栈顶元素
Status Push(SqStack S,SElemType e); //插入元素e
Status Pop(SqStack &S,SElemType &e); //删除栈顶元素并用e返回他的值
Status StackTraverse(SqStack S,Status (*visit)());
有些函数参数是通过引用传递的,使用了指针(地址)来传递参数,例如:
Status InitStack(SqStack &S)
Status DestroyStack(SqStack &S)
Status ClearStack(SqStack &S)
Status Pop(SqStack &S, SElemType &e)
这些函数对栈结构进行修改,因此需要传递栈的地址作为参数,以便在函数内部对栈进行操作。
其他函数的参数是通过值传递的,例如:
Status StackEmpty(SqStack S)
int StackLength(SqStack S)
Status GetTop(SqStack S, SElemType &e)
Status Push(SqStack S, SElemType e)
这些函数只是读取栈的状态或进行简单操作,不需要修改栈的结构,所以直接传递栈的值作为参数即可。
基本操作的部分实现:
Status InitStack(SqStack &S){
S.base=(SElemType *)malloc (STACK_INIT_SIZE * sizeof(SElemType));
if (!S.base)exit(OVERFLOW);
S.top=S.base;
S.stacksize=STACK_INIT_SIZE;
return OK;
}//InitStack
Status GetTop(SqStack S,SElemType &e){
if (S.top==S.base) return ERROE;
e =*(S.top-1);
return OK;
}
Status DestoryStack(SqStack &S) {
if (S.base != NULL) {
free(S.base); // 释放栈的内存空间
S.base = NULL; // 将栈底指针置空
S.top = NULL; // 将栈顶指针置空
S.stacksize = 0; // 栈大小置为0
return OK;
} else {
return ERROR; // 栈不存在,无法销毁
}
}
1.2.2 栈的链式表示
typedef struct StackNode {
SElemType data; // 数据元素
struct StackNode *next; // 指向下一个节点的指针
} StackNode;
typedef struct {
StackNode *top; // 栈顶指针
} LinkStack;
// 初始化栈
void InitStack(LinkStack *S) {
S->top = NULL; // 将栈顶指针置空
}
// 判断栈是否为空
int StackEmpty(LinkStack *S) {
return S->top == NULL; // 栈为空时返回1,否则返回0
}
// 入栈
void Push(LinkStack *S, SElemType e) {
StackNode *newNode = (StackNode *)malloc(sizeof(StackNode)); // 创建新节点
newNode->data = e; // 设置节点的数据元素
newNode->next = S->top; // 将新节点的next指针指向栈顶节点
S->top = newNode; // 更新栈顶指针
}
// 出栈
int Pop(LinkStack *S, SElemType *e) {
if (S->top == NULL) {
return 0; // 栈为空,出栈失败
}
StackNode *temp = S->top; // 保存栈顶节点的指针
*e = temp->data; // 获取栈顶节点的数据元素
S->top = temp->next; // 更新栈顶指针为下一个节点
free(temp); // 释放原栈顶节点的内存空间
return 1; // 出栈成功
}
// 获取栈顶元素
int GetTop(LinkStack *S, SElemType *e) {
if (S->top == NULL) {
return 0; // 栈为空,获取栈顶元素失败
}
*e = S->top->data; // 获取栈顶节点的数据元素
return 1; // 获取成功
}
1.3 栈的应用
1.3.1 数制转换
#include <stdio.h>
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef struct {
int *base;
int *top;
int stacksize;
} SqStack;
typedef enum {
OK,
ERROR
} Status;
Status InitStack(SqStack &S) {
S.base = (int *)malloc(STACK_INIT_SIZE * sizeof(int));
if (!S.base) {
return ERROR; // 内存分配失败
}
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
Status StackEmpty(SqStack S) {
return S.top == S.base;
}
Status Push(SqStack &S, int e) {
if (S.top - S.base >= S.stacksize) {
S.base = (int *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(int));
if (!S.base) {
return ERROR; // 内存分配失败
}
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*(S.top++) = e;
return OK;
}
Status Pop(SqStack &S, int &e) {
if (S.top == S.base) {
return ERROR; // 栈为空
}
e = *(--S.top);
return OK;
}
void DecimalToOtherBase(int num, int base) {
SqStack S;
InitStack(S);
// 将每个位的数字入栈
while (num != 0) {
int remainder = num % base;
Push(S, remainder);
num /= base;
}
// 出栈并打印
while (!StackEmpty(S)) {
int digit;
Pop(S, digit);
if (digit < 10) {
printf("%d", digit);
} else {
printf("%c", 'A' + digit - 10); // 十六进制使用字母表示大于9的数字
}
}
printf("\n");
}
int main() {
int num = 123;
int base = 2; // 转换为二进制
DecimalToOtherBase(num, base);
base = 8; // 转换为八进制
DecimalToOtherBase(num, base);
base = 16; // 转换为十六进制
DecimalToOtherBase(num, base);
return 0;
}
我们使用了给定的栈结构 SqStack
来实现栈的基本操作。InitStack
函数用于初始化栈,StackEmpty
函数用于判断栈是否为空,Push
函数用于入栈,Pop
函数用于出栈。
然后,我们定义了 DecimalToOtherBase
函数来进行数制转换。使用栈将每个位的数字入栈,然后出栈并根据进制要求打印对应的数字或字母。
通过条件判断 if (digit < 10)
,判断出栈的元素是否小于 10。如果小于 10,表示是一个数字,直接使用 printf("%d", digit)
打印该数字。
如果出栈的元素大于等于 10,表示是十六进制中的大于 9 的数字,为了表示这些数字,使用字母 A 开始,所以我们使用 printf("%c", 'A' + digit - 10)
打印对应的字母。
循环继续执行,直到栈中的所有元素都出栈完毕,打印完成后,程序结束。
最后,在 main
函数中,我们将十进制数 123 分别转换为二进制、八进制和十六进制,并打印转换结果。
1.3.2 括号匹配
#include <stdio.h>
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef struct {
char *base;
char *top;
int stacksize;
} SqStack;
typedef enum {
OK,
ERROR
} Status;
Status InitStack(SqStack *S) {
S->base = (char *)malloc(STACK_INIT_SIZE * sizeof(char));
if (!S->base) {
return ERROR; // 内存分配失败
}
S->top = S->base;
S->stacksize = STACK_INIT_SIZE;
return OK;
}
Status StackEmpty(SqStack S) {
return S.top == S.base;
}
Status Push(SqStack *S, char e) {
if (S->top - S->base >= S->stacksize) {
S->base = (char *)realloc(S->base, (S->stacksize + STACKINCREMENT) * sizeof(char));
if (!S->base) {
return ERROR; // 内存分配失败
}
S->top = S->base + S->stacksize;
S->stacksize += STACKINCREMENT;
}
*(S->top++) = e;
return OK;
}
Status Pop(SqStack *S, char *e) {
if (S->top == S->base) {
return ERROR; // 栈为空
}
*e = *(--S->top);
return OK;
}
Status MatchingBrackets(char *expression) {
SqStack S;
InitStack(&S);
int i = 0;
char ch;
while (expression[i] != '\0') {
if (expression[i] == '(' || expression[i] == '[' || expression[i] == '{') {
Push(&S, expression[i]);
} else if (expression[i] == ')' || expression[i] == ']' || expression[i] == '}') {
if (StackEmpty(S)) {
return ERROR; // 右括号多于左括号
}
char top;
Pop(&S, &top);
if ((expression[i] == ')' && top != '(') ||
(expression[i] == ']' && top != '[') ||
(expression[i] == '}' && top != '{')) {
return ERROR; // 括号不匹配
}
}
i++;
}
if (!StackEmpty(S)) {
return ERROR; // 左括号多于右括号
}
return OK;
}
int main() {
char expression[100];
printf("Enter an expression: ");
scanf("%s", expression);
if (MatchingBrackets(expression)) {
printf("Brackets are matched and nested correctly.\n");
} else {
printf("Brackets are not matched or not nested correctly.\n");
}
return 0;
}
InitStack
函数用于初始化栈,StackEmpty
函数用于判断栈是否为空,Push
函数用于入栈,Pop
函数用于出栈。
然后,我们定义了 MatchingBrackets
函数来检查括号是否匹配和正确嵌套。在该函数中,我们遍历输入的表达式,并根据遇到的括号类型进行相应的处理:遇到左括号则入栈,遇到右括号则与栈顶元素进行匹配判断。最后,通过判断栈是否为空来确定括号是否匹配和正确嵌套。
在 main
函数中,我们获取用户输入的表达式,并调用 MatchingBrackets
函数进行括号匹配检查,然后输出结果。
总结
- 栈是一种具有特定操作顺序的数据结构,遵循后进先出(LIFO)的原则。栈中元素的插入和删除操作只发生在栈的一端,称为栈顶。
- 栈的基本操作包括:初始化栈、销毁栈、清空栈、判断栈是否为空、获取栈的长度、获取栈顶元素、入栈操作和出栈操作。
- 栈可以使用数组(顺序栈)或链表(链式栈)来实现。顺序栈使用数组来存储元素,链式栈使用链表来存储元素。
- 数制转换是栈的一个常见应用。通过使用栈来实现数制转换,可以将一个十进制数转换为二进制、八进制或十六进制等其他进制表示。
- 括号匹配是另一个常见的栈应用。通过使用栈来检查括号是否匹配和正确嵌套,可以验证表达式中的括号是否符合语法规则。
- 在实现栈的过程中,需要注意栈的初始化、动态扩容(顺序栈)、内存管理(动态分配内存)以及栈空间的释放等细节问题。
- 使用栈解决问题时,通常需要考虑栈的空间复杂度和时间复杂度,以及处理边界情况和异常情况的健壮性。
- 了解栈的基本概念、操作和应用,以及栈的实现方式和适用场景,对于算法和数据结构的学习和编程实践都具有重要意义。