[翻譯]C#資料結構與演算法 – 第五章棧與隊列(Part 1)

來源:互聯網
上載者:User

5 棧與隊列

以列表組織資料是很自然的方式。之前我們使用Array與ArrayList將資料作為列表組織。雖然這些資料結構協助我們將資料以一種適合處理的格式組織,但沒有一種結構提供了一種真實的抽象來實際地設計與實現問題的解決方案。

棧與隊列是兩種面向列表資料結構,其提供了易於理解的抽象。棧中的資料添加與移除都是由列表的一端進行,而隊列中的資料由列表的一端添加並由列表的另一端移除。棧在程式設計語言的實現中廣泛使用,從運算式評估到函數調用等一切問題。隊列用於處理作業系統進程的優先順序調用及類比顯示世界中事件的發生,如銀行的收銀台及大樓中的電梯操作。

C#提供了兩個類來使用這兩個資料結構:Stack類與Queue類。我們將討論怎樣使用這些類然後看一下本章中一些實際的例子。

棧,棧的實現與STACK

棧是最常使用的資料結構,就像我們剛剛提到的那樣。我們將棧定義為一個項目的列表,其僅可以由列表的尾部訪問,這個尾部我們稱為棧的頂部。一個棧的標準模式就像一個自助餐廳的一摞餐盤。盤子總是由一摞的頂部被取走,當洗碗工或服務生把盤子放回時,仍然是放回頂部。棧是一種被稱作後進先出(LIFO)的資料結構。

棧操作

棧的兩個最主要操作是向棧添加項與由棧中移除項。Push操作向棧中添加一個項,Pop操作由棧中移除一個項。圖5.1展示了這些操作。

另一個在棧上完成的主要操作是查看頂部元素。Pop操作雖返回頂部元素,但是此操作也將這個元素由棧頂部移除。我們僅是要在不移除其的情況下來查看頂部的元素。在C#中這個操作名為Peek,在其他語言或實現中這個名稱可能不同(如為Top)。

Push,Pop與Peek是使用棧時我們進行的主要操作;同時,還有許多其它的方法及屬性需要我們學習並嘗試操作。由一個棧中一次移除所有項是很有用的。通過調用Clear操作可以將一個棧完全清空。隨時可以擷取到棧中項的數目也是很有用的。通過調用Count屬性可以實現這個目的。許多操作都通過一個返回true或false的StackEmpty方法來判斷棧的狀態,我們也可以使用Count屬性實現同樣的目的。

.NET Framework中的Stack類實現了以上提到的這些以及更多的屬性與方法,但是在我們學習使用Stack之前,先來瞭解一下如果沒有Stack類,我們怎樣實現一個棧。

一個棧類的實現

一個棧的實現需要使用底層的資料結構來儲存資料。我們將選擇ArrayList因為這樣做我們就無需為當新項被入棧時調整列表的大小而發愁。

由於C#擁有極好的物件導向編程的特性,我們將這個棧實現為一個類,稱作CStack。這個類中我們將實現一個建構函式,上面提到的那些方法以及Count這個屬性以展示怎樣在C#中完成這個工作。

首先讓我們實現這個類中需要的私人資料成員。

這個類中最主要的變數是一個用來儲存棧項目的ArrayList對象。我們需要跟蹤的僅有的另一個資料是棧的頂部,我們將用一個簡單的整型變數來用作索引。這個變數在一個CStack對象初始化時被設定為-1,每當一個項目被壓入棧,這個變數將增1。

建構函式除了將索引變數初始化-1外不再做其它工作。第一個要實現的方法是Push,其代碼調用ArrayList的Add方法將要添加的值傳遞給ArrayList。Pop方法完成3個工作:首先調用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];

}

}

接下來讓我們使用這段程式碼完成一個使用棧解決問題的程式。

迴文是一種正向與反向拼字都相同的字串。如:”dad”,”madam”與”sees”都是迴文,而像”hello”就不是迴文。一種檢查字串是否為迴文的方式就是使用棧。一般的演算法是逐個字元的讀取字串並在讀取時將每個字元如棧。這起到了一個將字串字元反向儲存的效果。下一步是將每一個字元出棧,同時由原始字串開始處比較相應的字元。如果在任何位置兩個字元不相同,字串就不是一個迴文,程式就可以停止。如果比較可以從頭進行到尾,這個字串就是一個迴文。

如下是程式,只列出Main函數,因為我們已定義了CStack類:

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();

}

Stack

Stack類是一個表現為LIFO的實現ICollection介面的集合類/棧。.NET Framework中的棧類被實現為一個迴圈緩衝器,其允許如棧的項所需的空間可以被動態分配。

Stack類中包含了用於入棧,出棧及擷取棧頂值的方法。同樣提供了擷取棧中元素數目的屬性,清空棧中值的方法,以及將棧轉換為數組的方法。首先我們來研究一下Stack類的建構函式。

Stack類建構函式

有三種方式來初始化一個Stack對象。預設建構函式初始化一個空的棧並將Capacity屬性初始化為10。預設建構函式以如下方式調用:

Stack myStack = new Stack();

一個泛型棧以如下方式初始化:

Stack<string> myStack = new Stack<string>();

每當棧中的元素數達到棧最大容量,容量會被加倍。

Stack的第二個建構函式允許你由另一個集合對象建立一個棧對象。例如,你可以向建構函式傳遞一個數組,一個棧將由已存在數組元素來建立:

string[] names = newstring[]{"Raymond","David", "Mike"};

Stack nameStack = new Stack(names);

執行Pop方法將首先由棧中移除”Mike”。

你也可以在初始化一個棧對象的同時指定棧的初始容量。當你可以提前知道要在棧中儲存多少個元素時這個建構函式就可以發揮作用。如果以這種方式來構建你的棧你可以使你的程式更高效。如果你的棧有20個元素並且已經達到最大容量,添加一個新元素將會觸發20+1次操作,因為每一個元素都需要被移動來適應新的元素。

在初始化棧同時指定初始大小的代碼如下:

Stack myStack = new Stack(25);

棧的主要操作

在棧上執行的主要操作為Push與Pop。使用Push方法將資料入棧。使用Pop方法完成資料出棧操作。讓我們在使用棧計算簡單的算術運算式這個問題環境中學習這些方法。

運算式求值程式使用兩個棧,其中一個用於運算元,另一個用於運算子。一個算術運算式儲存於被作為字串儲存。通過使用for迴圈讀取運算式中的每個字元,我們將字串分析為單獨的符號。如果符號是一個數字,將其放入數字棧。如果符號是一個運算子,將其放入運算子棧。因為我們執行中綴運算式,在執行一個操作之前我們需要等待兩個運算元入棧完成。在這一時刻,我們將兩個運算元前後出棧並執行指定的算術運算。結果被重新入棧來作為下一次計算的第一個運算式。這個過程持續到我們對所有的數字完成入棧與出棧操作。

如下是代碼:

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+$");

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;

}

}

}

}

使用Stack完成尾碼運算式的算數計算更簡單。在練習中你將有機會實現一個尾碼運算式計算機。

Peek方法

Peek方法允許我們查看棧頂元素的值而無需將項由棧中移除。如果沒有這個方法,當你需要擷取棧頂項的值時你不得不將此項出棧。在你需要將項出棧之前你將需要此方法來查看棧頂元素的值:

if (IsNumeric(Nums.Peek()))

num = Nums.Pop();

Clear方法

Clear方法由棧中移除所有項目,將項的數目設定為0。很難講Clear方法是否會影響棧的最大容量屬性,因為我們無法查看棧的實際最大容量,所以最好假定棧的最大容量被設定回初始的預設大小—10個元素。

Clear方法的一個最大作用在處理過程中發生錯誤時清空一個棧。如,在我們的運算式求值程式中,如果發生除0操作,就是一個錯誤,我們需要清空棧:

if (oper2 == 0)

Nums.Clear();

Contains方法

Contains方法可以判斷一個指定的元素是否位於棧中。如果元素被找到此方法返回true,反之返回false。我們可以用這個方法尋找一個當前並不位於棧頂的值,如這種情況:可以判斷一個會引起處理錯誤的字元是否存在於棧中:

if (myStack.Contains(" "))

StopProcessing();

else

ContinueProcessing();

CopyTo與ToArray方法

CopyTo方法將一個棧中的內容拷貝到一個數組。數組必須為Object類型,因為這是所有棧對象的資料類型。這個方法接受兩個參數:一個數組及一個索引表示由數組中這個位置開始放置拷貝的元素。元素以LIFO的順序被拷貝,就如它們被出棧的順序一般。下面程式碼片段展示了CopyTo方法的調用:

Stack myStack = new Stack();

for (int i = 20; i > 0; i--)

myStack.Push(i);

object[] myArray = new object[myStack.Count];

myStack.CopyTo(myArray, 0);

ToArray方法以一種類似的方式工作。你不可以在數組中指定一個起始位置,同時必須在指派陳述式中建立一個新數組。見如下樣本:

Stack myStack = new Stack();

for (int i = 0; i > 0; i++)

myStack.Push(i);

object[] myArray = new object[myStack.Count];

myArray = myStack.ToArray();

Stack類樣本:十進位到多種進位轉換

雖然商業應用中10進位數有廣泛的使用,但部分科學與技術應用中需要數字以其它進位方式來表示。許多電腦系統應用需要8進位或2進位格式數字。

一種將十進位像二進位或八進位轉換的演算法是使用棧。演算法步驟如下:

Ø 擷取數字

Ø 擷取進位

Ø 迴圈

Ø 將數字與進位的相除的餘數入棧

Ø 將數字置為原數字除以進位後的整數部分

Ø 當數字不為0的情況下繼續迴圈

當迴圈結束時,你需要轉換數字,你可以簡單的將單獨的數字出棧來查看結果。下面的程式展示了一個實現:

using System;

using System.Collections;

namespace csstack

{

class Class1

{

static void Main(string[] args)

{

int num, baseNum;

Console.Write("Enter a decimal number: ");

num = Convert.ToInt32(Console.ReadLine());

Console.Write("Enter a base: ");

baseNum = Convert.ToInt32(Console.ReadLine());

Console.Write(num + " converts to ");

MulBase(num, baseNum);

Console.WriteLine(" Base " + baseNum);

Console.Read();

}

static void MulBase(int n, int b)

{

Stack Digits = new Stack();

do

{

Digits.Push(n % b);

n /= b;

}

while (n != 0);

while (Digits.Count > 0)

Console.Write(Digits.Pop());

}

}

}

這個程式展示了為什麼對於大部分電腦問題,棧是一個有用的資料結構。當我們將十進位向其它格式轉換時,我們由最右端的數字開始一直向左計算。在這個過程中把每一個數字入棧,因為這樣當計算結束時,轉換後的數字將是正確的順序。

雖然棧是一種有用的資料結構,一些程式的模型更適合其它基於清單類型的資料結構。例如:雜貨店或周圍的音像租賃商店中排的隊。不像一個棧的後進先出的工作方式,隊列中先進的應該先出(FIFO)。另一個例子是發送給一個網路(或本地)印表機的列印工作的列表。第一個發送給印表機的作業應該被第一個處理。這些例子被模型化一種稱作隊列的基於列表的資料結構,這是下一節的主題。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.