文章目录
- 引言
- 一、栈的概念
- 二、栈的模拟实现
- 2.1 定义
- 2.2 初始化
- 2.3 销毁
- 2.4 压栈
- 2.5 判空
- 2.6 出栈
- 2.7 获取栈顶元素
- 2.8 获取栈的元素个数
- 2.9 元素访问
- 三、栈的应用场景
引言
数据结构世界——栈(Stack)
一、栈的概念
栈,是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈
:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈
:栈的删除操作叫做出栈。出数据也在栈顶。
二、栈的模拟实现
栈的实现一般可以使用数组
或者链表
实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
2.1 定义
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;
- 当前元素个数用栈顶top来表示,是栈独有的表示方法
2.2 初始化
void STInit(ST* pst)
{assert(pst);pst->a = NULL;pst->top = 0;//top指向栈顶元素的下一个位置pst->capacity = 0;
}
- 栈的初始化,top可以为0或-1,top为0,则指向栈顶元素的下一个位置,top为-1,则指向栈顶元素的位置
- 这里选用top为0,因为后面写的适合方便理解
2.3 销毁
void STDestroy(ST* pst)
{assert(pst);free(pst->a);pst->top = pst->capacity = 0;
}
- 栈的销毁和顺序表一样,直接释放数组空间,将top和capacity置为0
2.4 压栈
void STPush(ST* pst, STDataType x)
{assert(pst);if (pst->top == pst->capacity){int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}pst->a = tmp;pst->capacity = newCapacity;}pst->a[pst->top++] = x;
}
- 先判断是否需要扩容,因为这里只有压栈函数需要判断扩容,所以就不用专门再写一个扩容函数
- 初始容量为4,后续每次进行2倍扩容(这样扩容比较合理,不会太频繁,也不会太浪费)
- 再将元素压栈,top++
ps:realloc
函数在pst为NULL
时,充当malloc
的作用,所以既可以为初始栈开辟空间,又可以扩容 。
2.5 判空
bool STEmpty(ST* pst)
{assert(pst);return pst->top == 0;
}
- 如果top为0,则栈为空,返回真;反之,返回假
2.6 出栈
void STPop(ST* pst)
{assert(pst);assert(!STEmpty(pst));pst->top--;
}
- 先
assert
断言判断,栈是否为空,因为top不能减到负数 - 再将top- -,掩盖栈顶元素
2.7 获取栈顶元素
STDataType STTop(ST* pst)
{assert(pst);assert(!STEmpty(pst));return pst->a[pst->top - 1];
}
- 先
assert
断言判断,栈是否为空,因为top不能减到负数 - 再返回栈顶元素,因为这里top指向的是栈顶元素的下一位,所以下标访问时top要减1
2.8 获取栈的元素个数
int STSize(ST* pst)
{assert(pst);return pst->top;
}
- top指向的是栈顶元素的下一位,所以数值上就是栈的元素个数
这里很多函数实现都很简单,有些操作直接外部对结构体都可以直接实现,但最后还是写成函数封装,因为top的含义有多重,防止别人使用时误解,导致使用错误 。
2.9 元素访问
栈中元素访问(打印),不是用函数实现。因为它的特殊结构,决定了它的元素不能从任意位置访问 ,必须符合后进先出原则才可以。
void TestStack1()
{ST st;//初始化STInit(&st);//压栈STPush(&st, 1);STPush(&st, 2);printf("%d\n", STTop(&st));STPop(&st);STPush(&st, 3);STPush(&st, 4);STPush(&st, 5);//打印while (!STEmpty(&st)){printf("%d\n", STTop(&st));STPop(&st);}//销毁STDestroy(&st);
}
- 通常用循环的方式进行访问,同时每访问一个元素,就将它弹出栈,在进行下一个元素的访问
三、栈的应用场景
-
函数调用与返回
在程序执行过程中,函数调用和返回是栈结构的一个典型应用。当函数被调用时,其返回地址、局部变量等信息被压入调用栈中;当函数返回时,这些信息从调用栈中弹出并恢复程序的执行状态。 -
浏览器的前进后退
在浏览器中浏览网页时,我们使用的前进和后退功能也是栈结构的一个应用。浏览器将用户访问过的网页地址保存在一个栈中,当用户点击后退按钮时,浏览器从栈顶弹出最近访问的网页地址并跳转到该网页;当用户点击前进按钮时,浏览器将之前弹出的网页地址重新压入栈中并跳转到该网页。 -
表达式求值
在编译器和解释器中,表达式求值也是栈结构的一个重要应用。编译器和解释器将表达式转换为逆波兰表示法(Reverse Polish Notation, RPN),并使用栈来存储和操作操作数和操作符,从而实现表达式的求值。