数据结构与算法:堆栈
一、堆栈
堆栈(Stack)是一组相同数据类型的组合,所有的操作均在堆栈的顶端进行,具有“先进后出”(First In Last Out,FILO)的特性。堆栈结构在计算机中的应用相当广泛,时常被用来解决计算机的问题,例如递归调用,子程序的调用等。堆栈的数据结构原理,类似于下图:
1、堆栈简介
谈到先进后出(First In Last Out)的概念,其实就如同自助餐中餐盘由桌面网上一个一个叠放,且取用时由最上面先拿,这就是典型堆栈概念的应用。由于堆栈是一种抽象数据结构(Abstract Data Type),它有一下特性:
- 只能从堆栈的顶端存取数据。
- 数据的存取符合“先进后出”的原则。
可以参考下面的堆栈操作示意图:
堆栈的基本运算入下表所示:
create | 创建一个空堆栈 |
push | 把数据存压入堆栈顶端,并返回新堆栈 |
pop | 从堆栈顶端弹出数据,并返回新堆栈 |
isEmpty | 判断堆栈是否为空堆栈,是则返回true,不是则返回false |
full | 判断堆栈是否已满,是则返回true,不是则返回false |
堆栈在程序设计领域中,包含数组结构与链表结构两种设计方式,下面分别介绍。
二、使用数组来实现堆栈
以数组结构来实现堆栈的好处是设计的算法都相当简单,但是如果堆栈本身的大小是变动的,而数组的大小只能事先定义好,那么数组规划太大了又浪费空间,规划太小了则又不够用。看下面的一个示例:使用数组结构来设计一个C#程序,并使用循环来控制准备压入或弹出的元素,模仿堆栈的各种操作,其中必须包括压入(push)与弹出(pop)方法,最后还要输出堆栈内所有的元素。代码如下:
定义堆栈类:
using System; namespace StackByArray { public class StackArray { // 使用数组来模拟堆栈的声明 private int[] Stack; // 定义指向堆栈顶端的指针 private int top; // 构造函数 public StackArray(int stackSize) { // 建立数组 Stack = new int[stackSize]; // 数组的索引从0开始,当数组为空时,堆栈的顶端指针为-1 top = -1; } /// <summary> /// 入栈 把指定的数据压入堆栈顶端 /// </summary> /// <param name="data"></param> /// <returns></returns> public bool Push(int data) { // 判断堆栈顶端的索引是否大于数组大小 if(top>=Stack.Length) { Console.WriteLine("堆栈已满,无法在加入"); return false; } else { // 将数据压入堆栈,同时top上移一位 Stack[++top] = data; return true; } } /// <summary> /// 判断堆栈是否为空堆栈,如果是则返回true,否则返回false /// </summary> /// <returns></returns> public bool IsEmpty() { return top == -1 ? true : false; } /// <summary> /// 出栈 从堆栈的顶端弹出数据 /// </summary> /// <returns></returns> public int Pop() { if(IsEmpty()) { return -1; } else { // 先将数据弹出,然后在将堆栈指针下移 return Stack[top--]; } } } }
在Main方法中调用:
using System; namespace StackByArray { class Program { static void Main(string[] args) { int value; StackArray stack = new StackArray(10); Console.WriteLine("按顺序输入10个数:"); for (int i = 0; i < 10; i++) { value = int.Parse(Console.ReadLine()); // 入栈 stack.Push(value); } Console.WriteLine("================="); while(!stack.IsEmpty()) { Console.WriteLine($"堆栈弹出顺序:{stack.Pop()}"); } Console.ReadKey(); } } }
程序输出结果:
三、使用链表来实现堆栈
虽然以数组结构来制作堆栈的好处是制作与设计的算法都相当简单,但是如果堆栈本身是变动的话,数组大小就无法事先规划声明。此时往往会考虑使用最大可能性的数组空间,但是这将造成内存空间的浪费,而用链表来制作堆栈的优点是随时可以动态改变链表的长度,不过缺点是设计时算法较为复杂。下面我们用链表来实现堆栈。
Node节点类:
namespace StackByLinked { /// <summary> /// 节点 /// </summary> public class Node { public int Data { get; set; } public Node Next { get; set; } public Node(int data) { Data = data; Next = null; } } }
堆栈类:
using System; namespace StackByLinked { public class StackLinked { // 定义指向堆栈底端的指针 public Node Front; // 定义指向堆栈顶端的指针 public Node Rear; /// <summary> /// 判断堆栈是否为空堆栈 /// </summary> /// <returns></returns> public bool IsEmpty() { return Front == null; } /// <summary> /// 打印输出堆栈的内容 /// </summary> public void Print() { Node current = Front; while(current!=null) { Console.Write(current.Data + " "); current = current.Next; } Console.WriteLine(); } /// <summary> /// 入栈 /// </summary> /// <param name="data"></param> /// <returns></returns> public void Push(int data) { Node newNode = new Node(data); if(IsEmpty()) { Front = newNode; Rear = newNode; } else { Rear.Next = newNode; Rear = newNode; } } /// <summary> /// 出栈 返回栈顶元素 /// </summary> /// <returns></returns> public int Pop() { Node newNode; if(IsEmpty()) { Console.WriteLine("当前为空队列"); return -1; } newNode = Front; if(newNode == Rear) { Front = null; Rear = null; Console.WriteLine("当前为空队列"); return -1; } else { while(newNode.Next!=Rear) { newNode = newNode.Next; } int value = Rear.Data; // newNode被null赋值,相当于移除了Rear newNode.Next = Rear.Next; Rear = newNode; return value; } } } }
Main方法调用:
using System; namespace StackByLinked { class Program { static void Main(string[] args) { StackLinked stack = new StackLinked(); int choice = 0; while(true) { Console.Write("0结束 1把数据加入堆栈 2从堆栈弹出数据:"); choice = int.Parse(Console.ReadLine()); if(choice==2) { int value= stack.Pop(); Console.WriteLine($"弹出的栈顶元素:{value}"); Console.WriteLine("数据弹出后堆栈中的内容"); stack.Print(); } else if(choice == 1) { Console.WriteLine("请输入要加入堆栈的数据:"); choice = int.Parse(Console.ReadLine()); stack.Push(choice); Console.WriteLine("数据加入后堆栈中的内容:"); stack.Print(); } else if(choice == 0) { break; } else { Console.WriteLine("输入错误"); } } Console.ReadKey(); } } }
程序运行结果: