C#資料結構與演算法2-C# 棧和佇列

firespeed發表於2024-10-26

一 棧和佇列

棧和佇列是非常重要的兩種資料結構,在軟體設計中應用很多。棧和佇列也是線性結構,線性表、棧和佇列這三種資料結構的資料元素以及資料元素間的邏輯關係完全相同,差別是線性表的操作不受限制,而棧和佇列的操作受到限制。棧的操作只能在表的一端進行,佇列的插入操作在表的一端進行而其它操作在表的另一端進行,所以,把棧和佇列稱為操作受限的線性表。

1 棧

棧(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表。棧的操作示意圖如圖所示。

2 BCL中的棧

C#2.0 一下版本只提供了非泛型的Stack類(儲存object型別)
C#2.0 提供了泛型的Stack類
重要的方法如下
1,Push()入棧(新增資料)
2,Pop()出棧(刪除資料,返回被刪除的資料)
3,Peek()取得棧頂的資料,不刪除
4,Clear()清空所有資料
4,Count取得棧中資料的個數

3 棧的介面定義

public interface IStack<T> {
int Count{get;}
int GetLength(); //求棧的長度
bool IsEmpty(); //判斷棧是否為空
void Clear(); //清空操作
void Push(T item); //入棧操作
T Pop(); //出棧操作
T Peek(); //取棧頂元素
}

4 棧的儲存和程式碼實現-1.1,順序棧

用一片連續的儲存空間來儲存棧中的資料元素(使用陣列),這樣的棧稱為順序棧(Sequence Stack)。類似於順序表,用一維陣列來存放順序棧中的資料元素。棧頂指示器 top 設在陣列下標為 0 的端, top 隨著插入和刪除而變化,當棧為空時,top=-1。下圖是順序棧的棧頂指示器 top 與棧中資料元素的關係圖。

5 棧的儲存和程式碼實現,順序棧

class SeqStack<T>:IStack<T>

{

private T[] data;

private int top;



public SeqStack(int size)

{

data = new T[size];

top = -1;

}



//預設構造陣列的最大容量為10

public SeqStack():this(10)

{

}



public int GetLength()

{

return top + 1;

}


public int Count

{

get

{

return GetLength();

}

}



public bool IsEmpty()

{

return top <= -1;

}



public void Clear()

{

top = -1;

Array.Clear(data,0,data.Length);

}



public void Push(T item)

{

data[top + 1] = item;

top++;

}

/// <summary>

/// 出棧

/// </summary>

/// <returns></returns>

public T Pop()

{

T temp = data[top];

top--;

return temp;

}



public T Peek()

{

return data[top];

}

}

6 棧的儲存和程式碼實現,鏈棧

棧的另外一種儲存方式是鏈式儲存,這樣的棧稱為鏈棧(Linked Stack)。鏈棧通常用單連結串列來表示,它的實現是單連結串列的簡化。所以,鏈棧結點的結構與單連結串列結點的結構一樣,如圖 3.3 所示。由於鏈棧的操作只是在一端進行,為了操作方便,把棧頂設在連結串列的頭部,並且不需要頭結點。

7 鏈棧-鏈棧結點實現

public class Node<T>
{
private T data; //資料域
private Node<T> next; //引用域
//構造器
public Node(T val, Node<T> p)
{
data = val;
next = p;
}

//構造器
public Node(Node<T> p)
{
next = p;
}

//構造器
public Node(T val)
{
data = val;
next = null;
}

//構造器
public Node()
{
data = default(T);
next = null;
}

//資料域屬
public T Data
{
get { return data; }
set { data = value; }
}
//引用域屬性
public Node<T> Next
{
get { return next; }
set { next = value; }
}
}


8 鏈棧-程式碼實現

圖片
把鏈棧看作一個泛型類,類名為 LinkStack。LinkStack類中有一個欄位 top 表示棧頂指示器。由於棧只能訪問棧頂的資料元素,而鏈棧的棧頂指示器又不能指示棧的資料元素的個數。所以,求鏈棧的長度時,必須把棧中的資料元素一個個出棧,每出棧一個資料元素,計數器就增加 1,但這樣會破壞棧的結構。為保留棧中的資料元素,需把出棧的資料元素先壓入另外一個棧,計算完長度後,再把資料元素壓入原來的棧。但這種演算法的空間複雜度和時間複雜度都很高,所以,以上兩種演算法都不是理想的解決方法。理想的解決方法是 LinkStack類增設一個欄位 num 表示鏈棧中結點的個數。

二 佇列

佇列(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的操作示意圖如圖所示。
在實際生活中有許多類似於佇列的例子。比如,排隊取錢,先來的先取,後來的排在隊尾。
佇列的操作是線性表操作的一個子集。佇列的操作主要包括在隊尾插入元素、在隊頭刪除元素、取隊頭元素和判斷佇列是否為空等。與棧一樣,佇列的運算是定義在邏輯結構層次上的,而運算的具體實現是建立在物理儲存結構層次上的。因此,把佇列的操作作為邏輯結構的一部分,每個操作的具體實現只有在確定了佇列的儲存結構之後才能完成。佇列的基本運算不
是它的全部運算,而是一些常用的基本運算。

1 BCL中的佇列

C#2.0 以下版本提供了非泛型的Queue類
C#2.0 提供了泛型Queue類
方法
1,Enqueue()入隊(放在隊尾)
2,Dequeue()出隊(移除隊首元素,並返回被移除的元素)
3,Peek()取得隊首的元素,不移除
4,Clear()清空元素
屬性
5,Count獲取佇列中元素的個數

2 佇列介面定義

public interface IQueue<T> {
int Count{get;}//取得佇列長度的屬性
int GetLength(); //求佇列的長度
bool IsEmpty(); //判斷對列是否為空
void Clear(); //清空佇列
void Enqueue(T item); //入隊
T Dequque(); //出隊
T Peek(); //取隊頭元素
}

3 順序佇列

用一片連續的儲存空間來儲存佇列中的資料元素,這樣的佇列稱為順序佇列(Sequence Queue)。類似於順序棧,用一維陣列來存放順序佇列中的資料元素。隊頭位置設在陣列下標為 0 的端,用 front 表示;隊尾位置設在陣列的另一端,用 rear 表示。front 和 rear 隨著插入和刪除而變化。當佇列為空時, front=rear=-1。
圖是順序佇列的兩個指示器與佇列中資料元素的關係圖。

4 順序佇列-(迴圈順序佇列)

如果再有一個資料元素入隊就會出現溢位。但事實上佇列中並未滿,還有空閒空間,把這種現象稱為“假溢位”。這是由於佇列“隊尾入隊頭出”的操作原則造成的。解決假溢位的方法是將順序佇列看成是首尾相接的迴圈結構,頭尾指示器的關係不變,這種佇列叫迴圈順序佇列(Circular sequence Queue)。迴圈佇列如圖所示。

5 順序佇列-程式碼實現

把迴圈順序佇列看作是一個泛型類,類名叫 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中的方法外,還需要實現判斷迴圈順序佇列是否已滿的成員方法。

6 鏈佇列

佇列的另外一種儲存方式是鏈式儲存,這樣的佇列稱為鏈佇列(Linked Queue)。同鏈棧一樣,鏈佇列通常用單連結串列來表示,它的實現是單連結串列的簡化。所以,鏈佇列的結點的結構與單連結串列一樣,如圖所示。由於鏈佇列的操作只是在一端進行,為了操作方便,把隊頭設在連結串列的頭部,並且不需要頭結點。

7 鏈佇列-鏈佇列結點類

public class Node<T>
{
private T data; //資料域
private Node<T> next; //引用域
//構造器
public Node(T val, Node<T> p)
{
data = val;
next = p;
}
//構造器
public Node(Node<T> p)
{
next = p;
}
//構造器
public Node(T val)
{
data = val;
next = null;
}
//構造器
public Node()
{
data = default(T);
next = null;
}
//資料域屬性
public T Data
{
get
{
return data;
}
set
{
data = value;
}
}
//引用域屬性
public Node<T> Next
{
get
{
return next;
}
set
{
next = value;
}
}
}


8 鏈佇列-程式碼實現

把鏈佇列看作一個泛型類,類名為 LinkQueue。LinkQueue類中有兩個欄位 front 和 rear,表示隊頭指示器和隊尾指示器。由於佇列只能訪問隊頭的資料元素,而鏈佇列的隊頭指示器和隊尾指示器又不能指示佇列的元素個數,所以,與鏈棧一樣,在 LinkQueue類增設一個欄位 num 表示鏈佇列中結點的個數。

9 棧和佇列的應用舉例

程式設計判斷一個字串是否是迴文。迴文是指一個字元序列以中間字元為基準兩邊字元完全相同,如字元序列“ ACBDEDBCA”是迴文。
演算法思想:判斷一個字元序列是否是迴文,就是把第一個字元與最後一個字元相比較,第二個字元與倒數第二個字元比較,依次類推,第 i 個字元與第 n-i個字元比較。如果每次比較都相等,則為迴文,如果某次比較不相等,就不是迴文。因此,可以把字元序列分別入佇列和棧,然後逐個出佇列和出棧並比較出佇列的字元和出棧的字元是否相等,若全部相等則該字元序列就是迴文,否則就不是迴文。

C# 資料結構與演算法 · 目錄
上一篇C# 線性表下一篇C# 串和陣列

相關文章