线性表、栈和队列这三种数据结构的数据元素以及数据元素间的逻辑关系完全相同,差别是线性表的操作不受限制,而栈和队列的操作受到限制。栈的操作只能在表的一端进行, 队列的插入操作在表的一端进行而其它操作在表的另一端进行,所以,把栈和队列称为操作受限的线性表。
栈(Stack)是操作限定在表的尾端进行的线性表。表尾由于要进行插入、删除等操作,所以,它具有特殊的含义,把表尾称为栈顶(Top) ,另一端是固定的,叫栈底(Bottom) 。当栈中没有数据元素时叫空栈(Empty Stack)。栈通常记为:S= (a 1 ,a 2 ,…,a n ),S是英文单词stack的第 1 个字母。a 1 为栈底元素,a n 为栈顶元素。这n个数据元素按照a 1 ,a 2 ,…,a n 的顺序依次入栈,而出栈的次序相反,a n 第一个出栈,a 1 最后一个出栈。所以,栈的操作是按照后进先出(Last In First Out,简称LIFO)或先进后出(First In Last Out,简称FILO)的原则进行的, 因此, 栈又称为LIFO表或FILO表。
栈的操作示意图如图所示。
栈的形式化定义为:栈(Stack)简记为 S,是一个二元组,
S = (D, R)
其中: D 是数据元素的有限集合;
R 是数据元素之间关系的有限集合。
由于栈只能在栈顶进行操作, 所以栈不能在栈的任意一个元素处插入或删除元素。因此,栈的操作是线性表操作的一个子集。栈的操作主要包括在栈顶插入元素和删除元素、取栈顶元素和判断栈是否为空等。与线性表一样,栈的运算是定义在逻辑结构层次上的,而运算的具体实现是建立在物理存储结构层次上的。因此,把栈的操作作为逻辑结构的一部分,而每个操作的具体实现只有在确定了栈的存储结构之后才能完成。
栈的接口定义如下所示。
public interface IStack<T>//栈的接口定义 { int GetLength(); //求栈的长度 bool IsEmpty(); //判断栈是否为空 void Clear(); //清空操作 void Push(T item); //入栈操作 T Pop(); //出栈操作 T GetTop(); //取栈顶元素 }
用一片连续的存储空间来存储栈中的数据元素,这样的栈称为顺序栈(Sequence Stack)。类似于顺序表,用一维数组来存放顺序栈中的数据元素。
顺序栈类 SeqStack<T>的实现说明如下所示。
public class SeqStack<T> : IStack<T>//顺序栈类 { private int max; //顺序栈的容量 private int top; //指示顺序栈的栈顶 private T[] data; //数组,用于存储顺序栈中的数据元素 public int Max { get { return max; } set { max = value; } } public int Top { get { return top; } } public T this[int index] { get { return data[index]; } set { data[index] = value; } } public SeqStack(int size) { data = new T[size]; max = size; top = -1; } public int GetLength() { return top + 1; } public bool IsEmpty() { return top == -1; } public bool IsFull() { return top == max - 1; } public void Clear() { top = -1; } public void Push(T item) { if (IsFull()) { Console.WriteLine("Stack is full"); return; } data[++top] = item; } public T Pop() { T tmp = default(T); if (IsEmpty()) { Console.WriteLine("Stack is empty"); return tmp; } tmp = data[top--]; return tmp; } public T GetTop() { if (IsEmpty()) { Console.WriteLine("Stack is empty"); return default(T); } return data[top]; } }
栈的另外一种存储方式是链式存储,这样的栈称为链栈(Linked Stack)。链栈通常用单链表来表示,它的实现是单链表的简化。所以,链栈结点的结构与单链表结点的结构一样,如图所示。由于链栈的操作只是在一端进行,为了操作方便,把栈顶设在链表的头部,并且不需要头结点。
把链栈看作一个泛型类,类名为 LinkStack<T>。LinkStack<T>类中有一个字段 top 表示栈顶指示器。由于栈只能访问栈顶的数据元素,而链栈的栈顶指示器又不能指示栈的数据元素的个数。所以,求链栈的长度时,必须把栈中的数据元素一个个出栈,每出栈一个数据元素,计数器就增加 1,但这样会破坏栈的结构。为保留栈中的数据元素, 需把出栈的数据元素先压入另外一个栈, 计算完长度后,再把数据元素压入原来的栈。但这种算法的空间复杂度和时间复杂度都很高,所以, 以上两种算法都不是理想的解决方法。 理想的解决方法是 LinkStack<T>类增设一个字段 num 表示链栈中结点的个数。
链栈类 LinkStack<T>的实现说明如下所示。
public class Node<T>//链栈结点类 { private T data; private Node<T> next; public T Data { get { return data; } set { data = value; } } public Node<T> Next { get { return next; } set { next = value; } } public Node(T val, Node<T> node) { data = val; next = node; } public Node(T val) { data = val; next = null; } public Node(Node<T> node) { data = default(T); next = node; } public Node() { data = default(T); next = null; } } public class LinkStack<T> : IStack<T> //链栈类的实现 { private Node<T> top; //栈顶指示器 private int num; //栈中结点的个数 public Node<T> Top { get { return top; } set { top = value; } } public int Num { get { return num; } set { num = value; } } public LinkStack() { top = null; num = 0; } public int GetLength() { return num; } public bool IsEmpty() { return top == null && num == 0; } public void Clear() { top = null; num = 0; } public void Push(T item) { Node<T> node = new Node<T>(item); if (top == null) { top = node; } else { top.Next = node; top = node; } ++num; } public T Pop() { if (IsEmpty()) { Console.WriteLine("Stack is empty!"); return default(T); } Node<T> p = top; top = top.Next; return p.Data; } public T GetTop() { if (IsEmpty()) { Console.WriteLine("Stack is empty!"); return default(T); } return top.Data; } }