定义
栈(Stack):只允许在一端进行插入或删除操作的线性表。首先栈是一种特殊的线性表,但是限定这种线性表只能在某一端进行插入和删除操作,如图3-1所示。
栈顶(top):线性表允许进行插入和删除的那一端。
栈底(bottom):固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表。
逻辑结构
添加元素只能在尾节点后添加,删除元素只能删除尾节点,查看节点也只能查看尾节点。
形象的说,栈是一个先进后出(LIFO)表,先进去的节点要等到后边进去的节点出来才能出来。
存储结构
栈的存储方式有:
- 顺序栈
- 链栈
顺序栈
栈的顺序存储称为顺序栈,它是利用一组地址连续的存储单元存放自栈底到栈顶的数据 元素,同时附设一个指针(top)指示当前栈顶的位置。
栈的顺序存储类型可描述为:
1 #define MaxSize 50 //定义栈中元素的最大个数 2 typedef struct{ 3 Elemtype data[MaxSize]; //存放找中元素 4 int top; // 栈顶指针 5 }SqStack;
栈顶指针:S.top,初始时设置S.top=-1;栈顶元素:S.data[S.top]。
进栈操作:栈不满时,栈顶指针先加1,再送值到栈顶元素。
出栈操作:栈非空时,先取栈顶元素值,再将栈顶指针减1。
栈空条件:S.top=-1;栈满条件:S.top==MaxSize-1;栈长:S.top+1。
由于顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能 发生栈上溢,此时应及时向用户报告消息,以便及时处理,避免出错
注意:这里栈顶指针指向的就是栈顶元素,所以进栈时的操作是S.data[++S.top]=x,出 栈时的操作是x=S.data[S.top--]。如果栈顶指针初始化为S.top=0,即栈顶指针指向栈顶元素 的下一个位置,则入栈操作变为S.data[S.top++]=x,出栈操作变为x=S.data[--S.top]。相应的 栈空、栈满条件也会发生变化。请读者仔细体会其中的不同之处,做题时也应灵活应变。、
共享栈(共享顺序栈)
利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数据空间,将两个栈的 栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如图3-3所示。
图3-3 两个顺序栈共享存储空间
两个栈的栈顶指针都指向栈顶元素,top0=-1 时0号桟为空,top1=MaxSize时1号栈为 空;仅当两个栈顶指针相邻(top1-top0=1) 时,判断为栈满。当0号栈进栈时top0先加1 再赋值,1号栈进栈时top1先减1再赋值;出栈时则刚好相反。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被 占满时才发生上溢。其存取数据的时间复杂度均为0(1),所以对存取效率没有什么影响。
链栈
釆用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且 不存在栈满上溢的情况。通常釆用单链表实现,并规定所有操作都是在单链表的表头进行的。 这里规定链栈没有头结点,Lhead指向栈顶元素,如图3-4所示。
图3-4 栈的链式存储
栈的链式存储类型可描述为
1 typedef struct Linknode{ 2 ElemType data; //数据域 3 struct Linknode *next; //指针域 4 } *LiStack; //栈类型定义
釆用链式存储,便于结点的插入与删除。链栈的操作与链表类似,
操作运算
栈的应用
使用栈的时候一般不用自己重新去写,因为STL给我们实现了一个很安全的栈,可以放心去使用。也可以用数组模拟一个,很简单。
编译器调用函数就用了栈结构,当第一个函数还没执行完毕,调用第二个函数的时候,编译器就会把第一个函数压栈,第二个函数调用完毕的时候,就会取栈顶函数,也就是第一个函数继续执行。
实现递归的工作原理
汇编程序设计和高级语言程序设计调用函数和被调用函数之间的链接和信息转换都是通过栈来实现的。
在一个函数中调用另一个函数时,系统需要做三件事:
- 将需要传递给被调用函数的实参和函数返回地址传递给被调用函数保存;
- 为被调用函数的局部变量分配存储区;
- 将控制转移移到被调用函数入口;
在调用完成后,系统需要完成三件事,结束相应的调用过程:
- 保存计算结果
- 释放局部变量存储区
- 根据被调用函数保存的返回地址返回到调用函数;
函数调用时利用栈的后进先出原则,每当函数调用时,系统分配一个存储区,并将该存储区压入栈顶。当函数调用完成时系统释放存储区,弹出栈顶元素。这个栈应该是程序的活动栈,在整个程序运行期中都有效。
递归函数和普通函数调用的区别是递归函数的调用函数被调用函数时同一个,所以在调用时系统需要为递归函数建立一个“递归工作栈”,作为整个递归函数运行时的数据存储区。每一层递归函数构成一条工作记录,工作记录的内容就是被调用函数的普通必须内容-返回地址+实参,另外多了局部变量。每进入一层递归函数就产生一条工作记录压入栈,每完成一层递归函数就弹出当前栈顶元素。正在运行的函数必须是栈顶的活动记录,被称为“活动记录”,并将指向当前活动记录的栈顶指针成为“当前环境指针”。
调用函数和被调用函数之间不一定只能传递值的引用,也可以传递地址,这取决与高级程序设计语言的设计。
参考链接:http://c.biancheng.net/cpp/html/2699.html
http://www.cppblog.com/cxiaojia/archive/2012/08/01/185913.html
https://books.google.com.sg/books?id=H3ktn0sp5LsC&pg=PA44&dq=%E6%A0%88+%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84&hl=zh-CN&sa=X&ved=0ahUKEwiCtJ7N8vLOAhUKu48KHVrfBKcQ6AEIIjAB#v=onepage&q=%E6%A0%88%20%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84&f=false