栈和队列是线性表的两个经典特例,它们都是操作受限的线性表,即操作位置需要满足各自的条件,因为这些条件的特殊性,使得实现各自的操作时过程简捷,效率更高。这两个数据结构的应用也非常广泛。
栈
自助餐厅里的一摞盘子就是常见的栈的例子。在栈中,只能在栈顶位置添加或删除数据项。排队是日常生活中常见的场景。人正在排队的队伍就是队列,如服务窗口前的等待服务的一排顾客就构成了一个队列。在队列中,数据项在一端进入,在另一端离开。
定义:栈(stack)是限定仅在一端进行插入和删除的线性表。能进行插入和删除的这一端称为栈顶(top),而另一端称为栈底(bottom)。
在栈顶插入一个元素称为入栈(push),进栈或压栈,从栈顶删除一个元素称为出栈(pop)或退栈。
注意:入栈和出栈操作只能在栈顶进行,实际上,只能看到栈顶元素,栈顶之下的所有元素都是不可见的,对非栈顶元素的任何访问都是不允许的。
特性:后进先出
栈的存储及实现
和线性表一样,栈也有两种主要的存储方式,分别是顺序存储和链式存储。顺序存储方式使用数组保存栈元素,得到的是顺序栈。链式存储方法使用单链表保存栈元素,得到的是链式栈。
顺序栈
在顺序栈中,栈中的元素保存在一维数组中,将栈底定义在数组下标为0的位置。还有一个变量标记栈顶位置。栈顶位置也称栈顶指针,不过它只是数组中的一个下标,并不是真正意义上的指针。
栈顶位置top具体指向数组中的那个位置呢?
它有两种不同的定义方式:一种是定义在紧邻栈顶元素的下一个空位置,另一种是定义在栈顶元素所在的位置。
时间复杂度:栈的入栈操作及出栈操作都在栈顶进行,所以入栈及出栈时都不需要移动栈中已有的元素,避免了顺序表中插入及删除操作时的数据移动。所以顺序栈入栈和出栈操作的时间复杂度都是O(1),判定栈空及栈满等操作的时间复杂度都是O(1)。
链式栈
链式栈可以看做是一个仅在表头位置进行操作的单链表。将头指针所指的这一端作为栈顶,表尾一端作为栈底。入栈操作及出栈操作都可以通过头指针完成。在链式栈中,可以只定义头指针,尾指针和头结点都可以不定义。
时间复杂度:与顺序栈一样入栈和出栈操作的时间复杂度都是O(1)
顺序栈与链式栈的比较
- 顺序栈需要预先申请一个固定长度的一维数组,并且自始至终全部占用,当栈中元素较少时,空间浪费较大。
- 链式栈长度可变,但是每个元素都需要一个指针域,这又产生了结构型开销。
- 两种实现方式没有本质区别,如果不能预先估算出栈中元素最大个数时,只能使用链式栈,而如果知道最大元素个数则可以使用顺序栈。
- 如果栈中入栈,出栈操作比较频繁,则采用链式栈时,会频繁得调用系统函数,申请、释放结点所占用的空间。若每个节点占用的空间较小,则多次申请、释放空间会导致内存中出现很多碎片。