现实生活中的事情往往都能总结归纳成一定的数据结构,例如餐馆中餐盘的堆叠和使用,羽毛球筒里装的羽毛球等都是典型的栈结构。而在.NET中,值类型在线程栈上进行分配,引用类型在托管堆上进行分配,本文所说的“栈”正是这种数据结构。栈和队列都是常用的数据结构,它们的逻辑结构与线性表相通,不同之处则在于操作受某种特殊限制。因此,栈和队列也被称为操作受限的线性表。这里,我们首先来了解一下栈。
1.1 栈的基本特征
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。其特点是:”后进先出“或”先进后出“。
1.2 栈的基本操作
(1)栈的插入操作,叫作进栈,也称压栈、入栈:
(2)栈的删除操作,叫作出栈,也有的叫作弹栈:
二、栈的基本实现
既然栈属于特殊的线性表,那么其实现也会有两种形式:顺序存储结构和链式存储结构。首先,对于Stack,我们希望能够提供以下几个方法供调用:
|
Stack<T>() |
创建一个空的栈 |
|
void Push(T s) |
往栈中添加一个新的元素 |
|
T Pop() |
移除并返回最近添加的元素 |
|
bool IsEmpty() |
栈是否为空 |
|
int Size() |
栈中元素的个数 |
2.1 栈的顺序存储实现
对于顺序存储,我们可以参照顺序表的实现方式,借助数组来存储各个数据元素,然后对这个数组进行一定的封装,提供指定的操作对数据元素进行插入和删除即可。
(1)入栈操作实现
/// <summary> /// 入栈 /// </summary> /// <param name="node">节点元素</param> public void Push(T node) { if (index == nodes.Length) { // 增大数组容量 ResizeCapacity(nodes.Length * 2); } nodes[index] = node; index++; }
借助数组来实现入栈操作,其关键之处就在于top指针的移动。这里index初始值为0,每次入栈一个则将index加1,即指向下一个即将入栈的位置。由于这里采用了动态扩容的机制,所以没有判断栈中元素个数是否达到了最大值。
(2)出栈操作实现
出栈操作需要先去的要出栈的元素,然后将index减1,即指向下一个即将出栈的元素的位置。
/// <summary> /// 出栈 /// </summary> /// <returns>出栈节点元素</returns> public T Pop() { if(index == 0) { return default(T); } T node = nodes[index - 1]; index--; nodes[index] = default(T); if (index > 0 && index == nodes.Length / 4) { // 缩小数组容量 ResizeCapacity(nodes.Length / 2); } return node; }
这里首先需要判断index是否已经到达了最小值,出栈的元素位置需要置为默认值(如果是int数组,那么会重置为0),最后返回出栈的元素对象。这里当元素个数小于数组的四分之一时会进行容量收缩操作。
(3)完整的类实现
/// <summary> /// 基于数组的栈实现 /// </summary> /// <typeparam name="T">类型</typeparam> public class MyArrayStack<T> { private T[] nodes; private int index; public MyArrayStack(int capacity) { this.nodes = new T[capacity]; this.index = 0; } /// <summary> /// 入栈 /// </summary> /// <param name="node">节点元素</param> public void Push(T node) { if (index == nodes.Length) { // 增大数组容量 ResizeCapacity(nodes.Length * 2); } nodes[index] = node; index++; } /// <summary> /// 出栈 /// </summary> /// <returns>出栈节点元素</returns> public T Pop() { if(index == 0) { return default(T); } T node = nodes[index - 1]; index--; nodes[index] = default(T); if (index > 0 && index == nodes.Length / 4) { // 缩小数组容量 ResizeCapacity(nodes.Length / 2); } return node; } /// <summary> /// 重置数组大小 /// </summary> /// <param name="newCapacity">新的容量</param> private void ResizeCapacity(int newCapacity) { T[] newNodes = new T[newCapacity]; if(newCapacity > nodes.Length) { for (int i = 0; i < nodes.Length; i++) { newNodes[i] = nodes[i]; } } else { for (int i = 0; i < newCapacity; i++) { newNodes[i] = nodes[i]; } } nodes = newNodes; } /// <summary> /// 栈是否为空 /// </summary> /// <returns>true/false</returns> public bool IsEmpty() { return this.index == 0; } /// <summary> /// 栈中节点个数 /// </summary> public int Size { get { return this.index; } } }