一、堆栈
堆栈(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();
}
}
}
程序运行结果: