C#数据结构和算法学习系列六----堆栈、堆栈的实现和应用

时间:2021-05-14 10:24:39

      堆栈和队列是两种面向表的数据结构,它们都提供了易于理解的抽象。堆栈中的数据只能在表的某一端进行添加和删除操作,反之队列中的数据则在表的一端进行添加操作而在表的另一端进行删除操作。堆栈被广泛用于从表达式计算到处理函数调用的任何编程语言的实现中。而队列则用在区分优先次序的操作系统处理以及模拟现实世界的事件方面,比如银行出纳柜台的队列,以及建筑物内电梯的操作。C#语言为使用这些数据结构提供了两种类:Stack 类和Queue 类。

1.堆栈。堆栈是最频繁用到的数据结构之一。这里把堆栈定义为数据项的列表,而且这些数据项只能从表的末端进行存取访问。可存取访问的这端被称为是栈顶。堆栈的标准模型是自助餐厅的盘子堆。人们始终要从顶部拿走盘子,而且当洗碗工或者杂工把盘子放回盘子堆的时候也是把它放在盘堆的顶部。堆栈是著名的后进先出(LIFO)数据结构。堆栈最基本的两种操作就是向堆栈内添加数据项以及从堆栈中删除数据项。Push(进栈)操作是向堆栈内添加数据项。而把数据项从堆栈内取走则用Pop(出栈)操作。堆栈的另外一种基本操作就是察看栈顶的数据项。Pop 操作会返回栈顶的数据项,但是此操作也会把此数据项从堆栈中移除。如果只是希望察看栈顶的数据项而不是真的要移除它,那么在C#语言中有一种名为Peek(取数)的操作可以实现。当然,此操作在其他语言和实现中可能采用其他的名称(比如Top)。进栈、出栈以及取数都是在使用堆栈时会执行的基本操作。但是,还有其他一些需要执行的操作以及需要检查的属性。从堆栈中移除全部数据项就是非常有用的操作。通过调用Clear(清除)操作可以把堆栈全部清空。此外,在任何时候都能知道堆栈内数据项的数量也是非常有用的。这可以通过调用Count(计数)属性来实现。许多实现都有StackEmpty 方法。此方法会根据堆栈的状态返回一个真值或假值,但是也可以采用Count 属性达到同样的目的。.NET 框架的Stack 类实现了全部这些操作和属性,甚至还要更多。但是在讨论如何使用它们之前,还是先来看看如果没有Stack 类,则需要如何实现一个堆栈。

(1).Stack 的实现需要采用一种潜在的结构来保存数据。既然在新数据项进栈的时候不需要担心调整表的大小,所以这里选择用ArrayList。因为C#语言拥有如此强大的面向对象的编程特征,所以这里将把堆栈作为一个类来实现。此类被称为是CStack。这里还会包括一个构造器方法以及有关上述提及操作的方法。为了说明在C#语言中实现的过程,Count 属性会作为一种属性来实现。所需要的最重要的变量就是用来存储堆栈数据项的ArrayList 对象。除此以外,另一个也需要关注的数据就是栈顶。这里将用一个简单的整型变量来处理以便提供类似索引的功能。当对一个新的CStack 对象实例化时,会把此变量的初始值设为-1。每次把新的数据项压入堆栈时,变量就会自加1。构造器方法只完成对索引变量初始化为-1 的操作。第一种实现的方法是Push。程序调用ArrayLsit 的Add 方法,并且把传递给它的数值添加到ArrayList 里面。Pop 方法完成三件事:调用RemoveAt 方法来取走栈顶的数据项(脱离ArrayList),索引变量自减1 操作,以及最终返回出栈的对象。Peek 方法是通过调用含有索引变量作为参数的Item 方法来实现的。Clear 方法则简单地调用ArrayList 类中同样的方法。既然不需要突发改变堆栈上数据项的数量,所以这里把Count属性写为只读的属性。代码如下所示:

class CStack
{
private int p_index;
private ArrayList list;
public CStack()
{
list = new ArrayList();
p_index = -1;
}
public int count
{
get
{
return list.Count;
}
}
public void push(object item)
{
list.Add(item);
p_index++;
}
public object pop()
{
object obj = list[p_index];
list.RemoveAt(p_index);
p_index--;
return obj;
}
public void clear()
{
list.Clear();
p_index = -1;
}
public object peek()
{
return list[p_index];
}
}

     利用回文测试如下

static void Main(string[] args)
{
CStack alist = new CStack();
string ch;
string word = "sees";
bool isPalindrome = true;
for (int x = 0; x < word.Length; x++)
alist.push(word.Substring(x, 1));
int pos = 0;
while (alist.count > 0)
{
ch = alist.pop().ToString();
if (ch != word.Substring(pos, 1))
{
isPalindrome = false;
break;
}
pos++;
}
if (isPalindrome)
Console.WriteLine(word + " is a palindrome.");
else
Console.WriteLine(word + " is not a palindrome.");
Console.Read();
}

(2).对堆栈最主要的操作就是Push 和Pop。用Push 方法把数据添加到堆栈里面。用Pop 方法把数据从堆栈中移除。下面通过用堆栈来计算简单的算术表达式的实例来了解一下这些方法。

using System;
using System.Collections;
using System.Text.RegularExpressions;
namespace csstack
{
class Class1
{
static void Main(string[] args)
{
Stack nums = new Stack();
Stack ops = new Stack();
string expression = "5 + 10 + 15 + 20";
Calculate(nums, ops, expression);
Console.WriteLine(nums.Pop());
Console.Read();
}
// IsNumeric isn't built into C# so we must define it
static bool IsNumeric(string input)
{
bool flag = true;
string pattern = (@"^\d+{1}quot;);
Regex validate = new Regex(pattern);
if (!validate.IsMatch(input))
{
flag = false;
}
return flag;
}
static void Calculate(Stack N, Stack O, string exp)
{
string ch, token = "";
for (int p = 0; p < exp.Length; p++)
{
ch = exp.Substring(p, 1);
if (IsNumeric(ch))
token += ch; //+=
if (ch == " " || p == (exp.Length - 1))
{
if (IsNumeric(token))
{
N.Push(token);
token = "";
}
}
else if (ch == "+" || ch == "-" || ch == "*" || ch == "/")
O.Push(ch);
if (N.Count == 2)
Compute(N, O);
}
}
static void Compute(Stack N, Stack O)
{
int oper1, oper2;
string oper;
oper1 = Convert.ToInt32(N.Pop());
oper2 = Convert.ToInt32(N.Pop());
oper = Convert.ToString(O.Pop());
switch (oper)
{
case "+":
N.Push(oper1 + oper2);
break;
case "-":
N.Push(oper1 - oper2);
break;
case "*":
N.Push(oper1 * oper2);
break;
case "/":
N.Push(oper1 / oper2);
break;
}
}
}
}

(3).堆栈常用方法。

        Peek 方法。Peek 方法会让人们在不把数据项移出堆栈的情况下看到栈顶数据项的值。

       Clear 方法。Clear 方法会把所有数据项从堆栈内移除,并且把数据项计数器设置为零。

       Contains 方法。Contains 方法用来确定指定的元素是否在堆栈内。如果找到该元素,那么此方法会返回True;否则就返回False。

       CopyTo 方法。CopyTo 方法会把堆栈内的内容复制到一个数组中。数组必须是Object 类型,因为这是所有堆栈对象的数据类型。此方法会取走两个参数:一个数组和开始放置堆栈元素的数组的起始索引。堆栈内元素按照LIFO 的顺序进行复制操作,就好像对它们进行出栈操作一样。

       ToArray 方法。ToArray 方法的工作原理与CopyTo 方法类似。但是用户无法指定数组的起始索引位置,而是需要在赋值语句中创建新的数组