栈是具有先入后出特征的线性数据结构。
在益智玩具汉诺塔中,为了移动圆盘,玩家总是需要先移动一个塔上最上层的圆盘。而移动到下一个塔后,那个圆盘则始终会出现在那个塔的最上方。
此时,每个塔都可以被视为一个栈。
栈的所有元素都是先进后出的:
- 当元素入栈(添加到栈中)后,该元素成为栈顶(最底部的元素则为栈底)
- 元素的出栈即是从栈中移除该元素
常用操作
在使用栈的过程中,入栈、出栈和访问栈顶元素是最常见的操作,而这些操作的时间复杂度均为O(1)
。
类似列表,栈可以基于数组或链表实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Stack<int> stack = new Stack<int>();
stack.Push(1); stack.Push(1); stack.Push(4); stack.Push(5); stack.Push(1); stack.Push(4);
int pop = stack.Pop();
int peek = stack.Peek();
int size = stack.Count;
bool isEmpty = stack.Count == 0;
|
实现
抛开先进后出的特性不谈,栈和列表、链表极为相似。所以,对列表和链表施加限制即可达到栈的效果。
基于列表
将列表的尾部元素定义为栈顶,在列表的尾部添加元素是入栈,移除则是出栈。在未触发扩容操作的情况下,时间复杂度均为O(1)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class ListStack { List<int> stack = [];
public void Push(int num) => stack.Add(num); public int Size() => stack.Count; public bool IsEmpty() => Size() == 0;
public int Peek() { if (IsEmpty()) throw new Exception(); return stack[Size() - 1]; }
public int Pop() { if (IsEmpty()) throw new Exception(); var peek = Peek(); stack.RemoveAt(Size() - 1); return peek; } }
|
列表诸如访问任意元素,插入等操作不属于栈的范畴。
基于链表
将链表的头节点视为栈顶,在链表的头部添加元素是入栈,移除则是出栈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class Node<T>(T item) { public T Item { get; set; } = item; public Node<T> Next { get; set; } }
public class LinkedStack<T> { private Node<T> first = null; private int index = 0; public void Push(T item) { Node<T> oldNode = first; first = new Node<T>(item); first.Next = oldNode; index++; }
public T Pop() { T item = first.Item; first = first.Next; index--; return item; }
public bool IsEmpty() => this.index == 0;
public int Size() => this.index; }
|
对比
基于列表的实现效率一般更高,但扩容操作会使它在添加元素时降低到O(n)
的效率。由于扩容操作在后期不容易触发,所以不用太关心。
基于链表实现的效率虽然没有列表那么高且占用空间略大,但好在稳定,不会因为扩容操作而突然降低效率。
基本应用
本文和右下角标有www.hello-algo.com
标识的图片采用 署名-非商业性使用-相同方式共享 4.0 国际 许可协议,转载请注明出处。