栈(deap)和队列是非常重要的两种数据结构,在软件设计中应用很多。栈和队列也是线性结构,线性表、栈和队列这三种数据结构的数据元素以及数据元素间的逻辑关系完全相同,差别是线性表的操作不受限制,而栈和队列的操作受到限制。
栈的操作只能在表的一端进行(栈顶)
队列的插入操作在表的一端进行(队首)而其它操作在表的另一端进行
所以,把栈和队列称为操作受限的线性表。
栈(Stack)是操作限定在表的尾端进行的线性表。表尾由于要进行插入、删除等操作,所以,它具有特殊的含义,把表尾称为栈顶( Top),另一端是固定的,叫栈底( Bottom)。当栈中没有数据元素时叫空栈(Empty Stack)。
栈通常记为: S= (a1,a2,…,an),S是英文单词stack的第 1 个字母。a1为栈底元素,an为栈顶元素。这n个数据元素按照a1,a2,…,an的顺序依次入栈,而出栈的次序相反,an第一个出栈,a1最后一个出栈。所以,栈的操作是按照后进先出(Last In First Out,简称LIFO)或先进后出(First In Last Out,简称FILO)的原则进行的,因此,栈又称为LIFO表或FILO表。栈的操作示意图如图所示。
栈的接口定义
public interface IStackDS
{
int Count { get; }
int GetLength();//求栈的长度
bool IsEmpty(); //判断栈是否为空
void Clear();//清空操作
void Push(T item);//入栈操作
T Pop();//出栈操作
T Peek(); //取栈顶元素
}
栈的存储和代码实现
///
/// 顺序栈
///
///
public class SeqStack : IStackDS
{
private T[] data;
private int top;
//构造函数
public SeqStack(int size)
{
data = new T[size];
top = -1;
}
public SeqStack() : this(10)
{
}
//栈中元素个数
public int Count
{
get { return top + 1; }
}
//清空栈
public void Clear()
{
top = -1;
}
//获取栈的长度
public int GetLength()
{
return Count;
}
//判断栈是否是空的
public bool IsEmpty()
{
return Count == 0;
}
//入栈
public void Push(T item)
{
data[top + 1] = item;
top++;
}
//出栈
public T Peek()
{
return data[top];
}
//出栈删除出栈元素
public T Pop()
{
T temp = data[top];
top--;
return temp;
}
}
栈的另外一种存储方式是链式存储,这样的栈称为链栈(Linked Stack)。链栈通常用单链表来表示,它的实现是单链表的简化
把链栈看作一个泛型类,类名为 LinkStack。 LinkStack类中有一个字段 top 表示栈顶指示器。由于栈只能访问栈顶的数据元素,而链栈的栈顶指示器又不能指示栈的数据元素的个数。所以,求链栈的长度时,必须把栈中的数据元素一个个出栈,每出栈一个数据元素,计数器就增加 1,但这样会破坏栈的结构。为保留栈中的数据元素,需把出栈的数据元素先压入另外一个栈,计算完长度后,再把数据元素压入原来的栈。但这种算法的空间复杂度和时间复杂度都很高,所以,以上两种算法都不是理想的解决方法。理想的解决方法是 LinkStack类增设一个字段 num 表示链栈中结点的个数。
节点的实现
///
/// 链栈节点
///
///
public class Node
{
private T data;
private Node next;
//构造方法
public Node()
{
data = default(T);
next = null;
}
public Node(T data)
{
this.data = data;
next = null;
}
public Node(T data,Node next)
{
this.data = data;
this.next = next;
}
public Node(Node next)
{
this.next = next;
data = default(T);
}
public T Data
{
get { return data; }
set { data = value; }
}
public Node Next
{
get { return next; }
set { next = value; }
}
}
链栈的实现
///
/// 链栈
///
///
public class LinkStack : IStackDS
{
private Node top;//栈顶节点
private int count = 0;//栈中的元素个数
//取得栈中元素个数
public int Count
{
get { return count; }
}
//清空栈
public void Clear()
{
count = 0;
top = null;
}
//取得栈中元素个数
public int GetLength()
{
return count;
}
//判断栈是否为空
public bool IsEmpty()
{
return count == 0;
}
//出栈 取得栈顶元素
public T Peek()
{
return top.Data;
}
//出栈 取得栈顶元素,然后删除
public T Pop()
{
T data = top.Data;
top = top.Next;
count--;
return data;
}
//入栈
public void Push(T item)
{
//把新添加的元素作为 栈顶节点
Node newNode = new Node(item);
newNode.Next = top;
top = newNode;
count++;
}
}
队列(Queue)是插入操作限定在表的尾部而其它操作限定在表的头部进行的线性表。把进行插入操作的表尾称为队尾(Rear),把进行其它操作的头部称为队头(Front)。当队列中没有数据元素时称为空队列(Empty Queue)。
队列通常记为: Q= (a1,a2,…,an),Q是英文单词queue的第 1 个字母。a1为队头元素,an为队尾元素。这n个元素是按照a1,a2,…,an的次序依次入队的,出对的次序与入队相同,a1第一个出队,an最后一个出队。所以,对列的操作是按照先进先出(First In First Out)或后进后出( Last In Last Out)的原则进行的,因此,队列又称为FIFO表或LILO表。队列Q的操作示意图如图所示。
在实际生活中有许多类似于队列的例子。比如,排队取钱,先来的先取,后来的排在队尾。
队列的操作是线性表操作的一个子集。队列的操作主要包括在队尾插入元素、在队头删除元素、取队头元素和判断队列是否为空等。与栈一样,队列的运算是定义在逻辑结构层次上的,而运算的具体实现是建立在物理存储结构层次上的。因此,把队列的操作作为逻辑结构的一部分,每个操作的具体实现只有在确定了队列的存储结构之后才能完成。队列的基本运算不是它的全部运算,而是一些常用的基本运算。
用一片连续的存储空间来存储队列中的数据元素,这样的队列称为顺序队列(Sequence Queue)。类似于顺序栈,用一维数组来存放顺序队列中的数据元素。队头位置设在数组下标为 0 的端,用 front 表示;队尾位置设在数组的另一端,用 rear 表示。 front 和 rear 随着插入和删除而变化。当队列为空时, front=rear=-1。
图是顺序队列的两个指示器与队列中数据元素的关系图。
如果再有一个数据元素入队就会出现溢出。但事实上队列中并未满,还有空闲空间,把这种现象称为“假溢出”。这是由于队列“队尾入队头出”的操作原则造成的。解决假溢出的方法是将顺序队列看成是首尾相接的循环结构,头尾指示器的关系不变,这种队列叫循环顺序队列(Circular sequence Queue)。循环队列如图所示。
把循环顺序队列看作是一个泛型类,类名叫 CSeqStack,“ C”是英文单词 circular 的第 1 个字母。 CSeqStack类实现了接口 IQueue。用数组来存储循环顺序队列中的元素,在 CSeqStack类中用字段 data 来表示。用字段maxsize 表示循环顺序队列的容量, maxsize 的值可以根据实际需要修改,这通过CSeqStack类的构造器中的参数 size 来实现,循环顺序队列中的元素由 data[0]开始依次顺序存放。字段 front 表示队头, front 的范围是 0 到 maxsize-1。字段 rear表示队尾,rear 的范围也是 0 到 maxsize-1。如果循环顺序队列为空,front=rear=-1。当执行入队列操作时需要判断循环顺序队列是否已满,如果循环顺序队列已满,(rear + 1) % maxsize==front , 循 环 顺 序 队 列 已 满 不 能 插 入 元 素 。 所 以 ,CSeqStack类除了要实现接口 IQueue中的方法外,还需要实现判断循环顺序队列是否已满的成员方法。
定义接口
interface IQueue
{
int Count { get; }//取得队列长度的属性
int GetLength();//求队列的长度
bool IsEmpty();//判断对列是否为空
void Clear();//清空队列
void Enqueue(T temp); //入队
T Dequeue();//出队
T Peek();//取队头元素
}
顺序队列的实现
///
/// 顺序队列
///
///
public class SeqQueue : IQueue
{
private T[] data;
private int count;//表示当前有多少个元素
private int front;//队首(队首元素索引-1)
private int rear;//队尾(队尾元素索引)
//构造函数
public SeqQueue(int size)
{
data = new T[size];
count = 0;
front = -1;
rear = -1;
}
public SeqQueue():this(10)
{
}
public int Count
{
get { return count; }
}
public bool IsEmpty()
{
return count == 0;
}
public void Clear()
{
count = 0;
front = rear = -1;
}
public int GetLength()
{
return count;
}
//入队
public void Enqueue(T item)
{
if (count == data.Length)
{
Console.WriteLine("队列已满");
}
else
{
//假溢出
if (rear == data.Length -1)
{
data[0] = item;
rear = 0;
}
else
{
data[rear + 1] = item;
rear++;
}
count++;
}
}
//出队(删除出队数据)
public T Dequeue()
{
if (count>0)
{
T temp = data[front + 1];
front++;
count--;
return temp;
}
else
{
Console.WriteLine("队列没有元素");
return default(T);
}
}
public T Peek()
{
if (count > 0)
{
T temp = data[front + 1];
return temp;
}
else
{
Console.WriteLine("队列没有元素");
return default(T);
}
}
}
队列的另外一种存储方式是链式存储,这样的队列称为链队列(Linked Queue)。同链栈一样,链队列通常用单链表来表示,它的实现是单链表的简化。所以,链队列的结点的结构与单链表一样,如图所示。由于链队列的操作只是在一端进行,为了操作方便,把队头设在链表的头部,并且不需要头结点。
节点实现
///
/// 链队列 节点
///
///
public class Node
{
private T data;
private Node next;
public Node(T data)
{
this.data = data;
next = null;
}
public T Data
{
get { return data; }
set { data = value; }
}
public Node Next
{
get { return next;}
set { next = value; }
}
}
链队列
public class LinkQueue : IQueue
{
private Node front;//队首节点
private Node rear;//队尾节点
private int count;//元素个数
public LinkQueue()
{
front = null;
rear = null;
count = 0;
}
public int GetLength()
{
return count;
}
public bool IsEmpty()
{
return count == 0;
}
public int Count
{
get { return count; }
}
public void Clear()
{
front = rear = null;
count = 0;
}
//入队
public void Enqueue(T item)
{
Node newNode = new Node(item);
if (count == 0)
{
front = rear = newNode;
}
else
{
//当有数据时
//放在链的最后面
rear.Next = newNode;
//newNode作为尾
rear = newNode;
}
count++;
}
//出队(删除元素)
public T Dequeue()
{
if (count == 0)
{
Console.WriteLine("队列为空");
return default(T);
}else if (count == 1)
{
T temp = front.Data;
Clear();
return temp;
}
else
{
T temp = front.Data;
front = front.Next;
count--;
return temp;
}
}
//出队
public T Peek()
{
if (front!= null)
{
return front.Data;
}
else
{
return default(T);
}
}
}