栈
栈是一种基于后进先出(LIFO)策略的集合类型,当放入数据时,压入(push)最顶端,当取数据时,从顶端弹出(pop)。
栈的基本操作
由此可以定义栈的基本操作:
- void push(Item item)
- Item pop()
- boolean isEmpty()
- int size()
以上就是栈支持的所有操作,这是一个严格的接口说明书,没有额外的多余操作,这是一个良好的习惯,仅提供必需的接口,简洁而清晰(有些语言的库函数提供了Stack功能,但是却提供了额外的操作,比如可以在栈顶和栈底添加和删除元素)。
栈的实现
依照上述的接口说明书,我们可以通过各种结构来实现栈,最容易想到的就是上篇文章提到的线性存储结构,数组和链表。
数组实现
构造固定容量的一个数组, 每次在数组的尾部进行添加和删除,当该固定容量数组达到上限,即数组已满,需要扩展数组容量(比如容量翻倍),把原来数据全部搬家到新数组,后续使用新数组继续操作。 随着数据的pop出栈,数据越来越少,数组空闲了很多空间,因此需要处理此种情况,当数据占用比例小于一定的数值,则需要重新申请一个小空间的数组,把数据搬家到此数组,把原来的大数组空间释放。
链表实现
使用前面文章描述的双循环链表,可以方便的实现栈所支持的操作。
初始状态,构造一个双循环链表,仅包含头节点。
void push(Item item)
从双循环链表的尾部添加节点。Item pop()
从双循环链表的尾部删除节点。boolean isEmpty()
判断头结点是否指向头结点。int size()
内部维护一个计数器,当push时增加1,当pop时,减小1。
依靠双循环链表的通用结构,方便的实现栈,时间和空间复杂度为O(1),简洁而高效。
栈的应用
栈是计算机科学中很重要的一种数据结构。如各种编程语言支持的函数调用过程,均是压栈的过程,函数调用返回就是出栈的过程。 后面将要谈到的递归, 是对栈使用过程的完美诠释。