一:棧
棧和隊列也是線性結構,線性表、棧和隊列這三種數據結構的數據元素以及數據元素間的邏輯關系完全相同,差別是線性表的操作不受限制,而棧和隊列的操作受到限制。棧的操作只能在表的一端進行,隊列的插入操作在表的一端進行而其它操作在表的另一端進行,所以,把棧和隊列稱為操作受限的線性表。
1:棧的定義及基本運算
棧(Stack)是操作限定在表的尾端進行的線性表。表尾由於要進行插入、刪除等操作,所以,它具有特殊的含義,把表尾稱為棧頂(Top),另一端是固定的,叫棧底(Bottom)。當棧中沒有數據元素時叫空棧(Empty Stack)。
棧通常記為:S= (a 1 ,a 2 ,…,a n ),S是英文單詞stack的第 1 個字母。a 1 為棧底元素,a n 為棧頂元素。這n個數據元素按照a 1 ,a 2 ,…,a n 的順序依次入棧,而出棧的次序相反,a n 第一個出棧,a 1 最后一個出棧。所以,棧的操作是按照后進先出(Last In First Out,簡稱LIFO)或先進后出(First In Last Out,簡稱FILO)的原則進行的,因此,棧又稱為LIFO表或FILO表。

由於棧只能在棧頂進行操作,所以棧不能在棧的任意一個元素處插入或刪除元素。因此,棧的操作是線性表操作的一個子集。棧的操作主要包括在棧頂插入元素和刪除元素、取棧頂元素和判斷棧是否為空等。
棧的接口定義如下所示:
1 public interface IStack<T> 2 { 3 int GetLength(); //求棧的長度 4 bool IsEmpty(); //判斷棧是否為空 5 void Clear(); //清空操作 6 void Push(T item); //入棧操作 7 T Pop(); //出棧操作 8 T GetTop(); //取棧頂元素 9 }
2:棧的存儲和運算實現
1、順序棧:
用一片連續的存儲空間來存儲棧中的數據元素,這樣的棧稱為順序棧(Sequence Stack)。類似於順序表,用一維數組來存放順序棧中的數據元素。

順序棧類 SeqStack<T>的實現說明如下所示。
1 public class SeqStack<T> : IStack<T> 2 { 3 private int maxsize; //順序棧的容量 4 private T[] data; //數組,用於存儲順序棧中的數據元素 5 private int top; //指示順序棧的棧頂 6 //索引器 7 public T this[int index] 8 { 9 get 10 { 11 return data[index]; 12 } 13 set 14 { 15 data[index] = value; 16 } 17 } 18 //容量屬性 19 public int Maxsize 20 { 21 get 22 { 23 return maxsize; 24 } 25 set 26 { 27 maxsize = value; 28 } 29 } 30 //棧頂屬性 31 public int Top 32 { 33 get 34 { 35 return top; 36 } 37 } 38 //構造器 39 public SeqStack(int size) 40 { 41 data = new T[size]; 42 maxsize = size; 43 top = -1; 44 } 45 //求棧的長度 46 public int GetLength() 47 { 48 return top + 1; 49 } 50 //清空順序棧 51 public void Clear() 52 { 53 top = -1; 54 } 55 //判斷順序棧是否為空 56 public bool IsEmpty() 57 { 58 if (top == -1) 59 { 60 return true; 61 } 62 else 63 { 64 return false; 65 } 66 } 67 //判斷順序棧是否為滿 68 public bool IsFull() 69 { 70 if (top == maxsize - 1) 71 { 72 return true; 73 } 74 else 75 { 76 return false; 77 } 78 } 79 //入棧 80 public void Push(T item) 81 { 82 if (IsFull()) 83 { 84 Console.WriteLine("Stack is full"); 85 return; 86 } 87 data[++top] = item; 88 } 89 //出棧 90 public T Pop() 91 { 92 T tmp = default(T); 93 if (IsEmpty()) 94 { 95 Console.WriteLine("Stack is empty"); 96 return tmp; 97 } 98 tmp = data[top]; 99 --top; 100 return tmp; 101 } 102 //獲取棧頂數據元素 103 public T GetTop() 104 { 105 if (IsEmpty()) 106 { 107 Console.WriteLine("Stack is empty!"); 108 return default(T); 109 } 110 return data[top]; 111 } 112 }
2、鏈棧
棧的另外一種存儲方式是鏈式存儲,這樣的棧稱為鏈棧(Linked Stack)。鏈棧通常用單鏈表來表示,它的實現是單鏈表的簡化。所以,鏈棧結點的結構與單鏈表結點的結構一樣,如圖 3.3 所示。由於鏈棧的操作只是在一端進行,為了操作方便,把棧頂設在鏈表的頭部,並且不需要頭結點。
鏈棧結點類(Node<T>)的實現如下:

1 public class Node<T> 2 { 3 private T data; //數據域 4 private Node<T> next; //引用域 5 //構造器 6 public Node(T val, Node<T> p) 7 { 8 data = val; 9 next = p; 10 } 11 //構造器 12 public Node(Node<T> p) 13 { 14 next = p; 15 } 16 //構造器 17 public Node(T val) 18 { 19 data = val; 20 next = null; 21 } 22 //構造器 23 public Node() 24 { 25 data = default(T); 26 next = null; 27 } 28 //數據域屬性 29 public T Data 30 { 31 get 32 { 33 return data; 34 } 35 set 36 { 37 data = value; 38 } 39 } 40 //引用域屬性 41 public Node<T> Next 42 { 43 get 44 { 45 return next; 46 } 47 set 48 { 49 next = value; 50 } 51 } 52 }

鏈棧類 LinkStack<T>的實現說明如下所示。
1 public class LinkStack<T> : IStack<T> 2 { 3 private Node<T> top; //棧頂指示器 4 private int num; //棧中結點的個數 5 //棧頂指示器屬性 6 public Node<T> Top 7 { 8 get 9 { 10 return top; 11 } 12 set 13 { 14 top = value; 15 } 16 } 17 //元素個數屬性 18 public int Num 19 { 20 get 21 { 22 return num; 23 } 24 set 25 { 26 num = value; 27 } 28 } 29 //構造器 30 public LinkStack() 31 { 32 top = null; 33 num = 0; 34 } 35 //求鏈棧的長度 36 public int GetLength() 37 { 38 return num; 39 } 40 //清空鏈棧 41 public void Clear() 42 { 43 top = null; 44 num = 0; 45 } 46 //判斷鏈棧是否為空 47 public bool IsEmpty() 48 { 49 if ((top == null) && (num == 0)) 50 { 51 return true; 52 } 53 else 54 { 55 return false; 56 } 57 } 58 //入棧 59 public void Push(T item) 60 { 61 Node<T> q = new Node<T>(item); 62 if (top == null) 63 { 64 top = q; 65 } 66 else 67 { 68 q.Next = top; 69 top = q; 70 } 71 ++num; 72 } 73 //出棧 74 public T Pop() 75 { 76 if (IsEmpty()) 77 { 78 Console.WriteLine("Stack is empty!"); 79 return default(T); 80 } 81 Node<T> p = top; 82 top = top.Next; 83 --num; 84 return p.Data; 85 } 86 //獲取棧頂結點的值 87 public T GetTop() 88 { 89 if (IsEmpty()) 90 { 91 Console.WriteLine("Stack is empty!"); 92 return default(T); 93 } 94 return top.Data; 95 } 96 }
3:C#中的棧
C#2.0 以下版本只提供了非泛型的 Stack 類,該類繼承了 ICollection、IEnumerable 和 ICloneable 接口。C#2.0 提供了泛型的 Stack<T>類,該類繼承了 IEnumerable<T>、ICollection 和 IEnumerable 接口。
二:隊列
1:隊列的定義及基本運算
隊列(Queue)是插入操作限定在表的尾部而其它操作限定在表的頭部進行的線性表。把進行插入操作的表尾稱為隊尾(Rear),把進行其它操作的頭部稱為隊頭(Front)。當對列中沒有數據元素時稱為空對列(Empty Queue)。
隊列通常記為:Q= (a 1 ,a 2 ,…,a n ),Q是英文單詞queue的第 1 個字母。a 1 為隊頭元素,a n 為隊尾元素。這n個元素是按照a 1 ,a 2 ,…,a n 的次序依次入隊的,出對的次序與入隊相同,a 1 第一個出隊,a n 最后一個出隊。所以,對列的操作是按照先進先出(First In First Out)或后進后出( Last In Last Out)的原則進行的,因此,隊列又稱為FIFO表或LILO表。隊列Q的操作如下圖。

隊列的操作是線性表操作的一個子集。隊列的操作主要包括在隊尾插入元素、在隊頭刪除元素、取隊頭元素和判斷隊列是否為空等。與棧一樣,隊列的運算是定義在邏輯結構層次上的,而運算的具體實現是建立在物理存儲結構層次上的。因此,把隊列的操作作為邏輯結構的一部分,每個操作的具體實現只有在確定了隊列的存儲結構之后才能完成。隊列的基本運算不是它的全部運算,而是一些常用的基本運算。
隊列接口 IQueue<T>的定義如下所示。
1 public interface IQueue<T> 2 { 3 int GetLength(); //求隊列的長度 4 bool IsEmpty(); //判斷對列是否為空 5 void Clear(); //清空隊列 6 void In(T item); //入隊 7 T Out(); //出隊 8 T GetFront(); //取對頭元素 9 }
2:隊列的存儲和運算實現
1、順序隊列
用一片連續的存儲空間來存儲隊列中的數據元素,這樣的隊列稱為順序隊列(Sequence Queue)。
類似於順序棧,用一維數組來存放順序隊列中的數據元素。隊頭位置設在數組下標為 0 的端,用 front 表示;隊尾位置設在數組的另一端,用 rear 表示。front 和 rear 隨着插入和刪除而變化。當隊列為空時,front=rear=-1。下圖是順序隊列的兩個指示器與隊列中數據元素的關系圖。

當有數據元素入隊時,隊尾指示器 rear 加 1,當有數據元素出隊時,隊頭指示器 front 加 1。當 front=rear 時,表示隊列為空,隊尾指示器 rear 到達數組的上限處而 front 為-1 時,隊列為滿,如圖(c)所示。隊尾指示器 rear 的值大於隊頭指示器 front 的值,隊列中元素的個數可以由 rear-front 求得。
由圖 (d)可知,如果再有一個數據元素入隊就會出現溢出。但事實上隊列中並未滿,還有空閑空間,把這種現象稱為“假溢出”。這是由於隊列“隊尾入隊頭出”的操作原則造成的。解決假溢出的方法是將順序隊列看成是首尾相接的循環結構,頭尾指示器的關系不變,這種隊列叫循環順序隊列( Circular sequenceQueue )。循環隊列如下圖。

當隊尾指示器 rear 到達數組的上限時,如果還有數據元素入隊並且數組的第0 個空間空閑時,隊尾指示器 rear 指向數組的 0 端。所以,隊尾指示器的加 1 操作修改為:
rear = (rear + 1) % maxsize
隊頭指示器的操作也是如此。當隊頭指示器 front 到達數組的上限時,如果還有數據元素出隊,隊頭指示器 front 指向數組的 0 端。所以,隊頭指示器的加1 操作修改為:
front = (front + 1) % maxsize
2:循環順序隊列操作示意圖如下

由圖可知,隊尾指示器 rear 的值不一定大於隊頭指示器 front 的值,並且隊滿和隊空時都有 rear=front。也就是說,隊滿和隊空的條件都是相同的。解決這個問題的方法一般是少用一個空間,如圖 (d)所示,把這種情況視為隊滿。所以,判斷隊空的條件是:rear==front,判斷隊滿的條件是:(rear + 1)% maxsize==front。求循環隊列中數據元素的個數可由(rear-front+maxsize)%maxsize公式求得。
1 public class CSeqQueue<T> : IQueue<T> 2 { 3 private int maxsize; //循環順序隊列的容量 4 private T[] data; //數組,用於存儲循環順序隊列中的數據元素 5 private int front; //指示循環順序隊列的隊頭 6 private int rear; //指示循環順序隊列的隊尾 7 //索引器 8 public T this[int index] 9 { 10 get 11 { 12 return data[index]; 13 } 14 set 15 { 16 data[index] = value; 17 } 18 } 19 //容量屬性 20 public int Maxsize 21 { 22 get 23 { 24 return maxsize; 25 } 26 set 27 { 28 maxsize = value; 29 } 30 } 31 //隊頭屬性 32 public int Front 33 { 34 get 35 { 36 return front; 37 } 38 set 39 { 40 front = value; 41 } 42 } 43 //隊尾屬性 44 public int Rear 45 { 46 get 47 { 48 return rear; 49 } 50 set 51 { 52 rear = value; 53 } 54 55 } 56 //構造器 57 public CSeqQueue(int size) 58 { 59 data = new T[size]; 60 maxsize = size; 61 front = rear = -1; 62 } 63 //求循環順序隊列的長度 64 public int GetLength() 65 { 66 return (rear - front + maxsize) % maxsize; 67 } 68 //清空循環順序隊列 69 public void Clear() 70 { 71 front = rear = -1; 72 } 73 //判斷循環順序隊列是否為空 74 public bool IsEmpty() 75 { 76 if (front == rear) 77 { 78 return true; 79 } 80 else 81 { 82 return false; 83 } 84 } 85 //判斷循環順序隊列是否為滿 86 public bool IsFull() 87 { 88 if ((rear + 1) % maxsize == front) 89 { 90 return true; 91 } 92 else 93 { 94 return false; 95 } 96 } 97 //入隊 98 public void In(T item) 99 { 100 if (IsFull()) 101 { 102 Console.WriteLine("Queue is full"); 103 return; 104 } 105 data[++rear] = item; 106 } 107 //出隊 108 public T Out() 109 { 110 T tmp = default(T); 111 if (IsEmpty()) 112 { 113 Console.WriteLine("Queue is empty"); 114 return tmp; 115 } 116 tmp = data[++front]; 117 return tmp; 118 } 119 //獲取隊頭數據元素 120 public T GetFront() 121 { 122 if (IsEmpty()) 123 { 124 Console.WriteLine("Queue is empty!"); 125 return default(T); 126 } 127 return data[front + 1]; 128 } 129 }
3:鏈隊列
隊列的另外一種存儲方式是鏈式存儲,這樣的隊列稱為鏈隊列(LinkedQueue)。同鏈棧一樣,鏈隊列通常用單鏈表來表示,它的實現是單鏈表的簡化。所以,鏈隊列的結點的結構與單鏈表一樣,由於鏈隊列的操作只是在一端進行,為了操作方便,把隊頭設在鏈表的頭部,並且不需要頭結點。
節點示意圖:

鏈隊列示意圖:

把鏈隊列看作一個泛型類,類名為 LinkQueue<T>。LinkQueue<T>類中有兩個字段 front 和 rear,表示隊頭指示器和隊尾指示器。由於隊列只能訪問隊頭的數據元素,而鏈隊列的隊頭指示器和隊尾指示器又不能指示隊列的元素個數,所以,與鏈棧一樣,在 LinkQueue<T>類增設一個字段 num 表示鏈隊列中結點的個數。鏈隊列類 LinkQueue<T>的實現說明如下所示。
1 public class LinkQueue<T> : IQueue<T> 2 { 3 private Node<T> front; //隊列頭指示器 4 private Node<T> rear; //隊列尾指示器 5 private int num; //隊列結點個數 6 //隊頭屬性 7 public Node<T> Front 8 { 9 get 10 { 11 return front; 12 } 13 set 14 { 15 front = value; 16 } 17 } 18 //隊尾屬性 19 public Node<T> Rear 20 { 21 get 22 { 23 return rear; 24 } 25 set 26 { 27 rear = value; 28 } 29 } 30 //隊列結點個數屬性 31 public int Num 32 { 33 get 34 { 35 return num; 36 } 37 set 38 { 39 num = value; 40 } 41 } 42 //構造器 43 public LinkQueue() 44 { 45 front = rear = null; 46 num = 0; 47 } 48 //求鏈隊列的長度 49 public int GetLength() 50 { 51 return num; 52 } 53 //清空鏈隊列 54 public void Clear() 55 { 56 front = rear = null; 57 num = 0; 58 } 59 //判斷鏈隊列是否為空 60 public bool IsEmpty() 61 { 62 if ((front == rear) && (num == 0)) 63 { 64 return true; 65 } 66 else 67 { 68 return false; 69 } 70 } 71 //入隊 72 public void In(T item) 73 { 74 Node<T> q = new Node<T>(item); 75 if (rear == null) 76 { 77 rear = q; 78 } 79 else 80 { 81 rear.Next = q; 82 rear = q; 83 } 84 ++num; 85 } 86 //出隊 87 public T Out() 88 { 89 if (IsEmpty()) 90 { 91 Console.WriteLine("Queue is empty!"); 92 return default(T); 93 } 94 Node<T> p = front; 95 front = front.Next; 96 if (front == null) 97 { 98 rear = null; 99 } 100 --num; 101 return p.Data; 102 } 103 //獲取鏈隊列頭結點的值 104 public T GetFront() 105 { 106 if (IsEmpty()) 107 { 108 Console.WriteLine("Queue is empty!"); 109 return default(T); 110 } 111 return front.Data; 112 } 113 }
3.1:C# 中的隊列
C#2.0 以下版本只提供了非泛型的 Queue 類,該類繼承了 ICollection、IEnumerable 和 ICloneable 接口。C#2.0 提供了泛型的 Queue<T>類,該類繼承了 IEnumerable<T>、ICollection 和 IEnumerable 接口。以下程序說明了泛型Queue<T>類的主要方法,並對在我們自定義的隊列類中沒有出現的成員方法進行了注釋,關於泛型 Queue<T>類的更具體的信息,讀者可參考.NET Framework的有關書籍。
三 : 小結
棧和隊列是計算機中常用的兩種數據結構,是操作受限的線性表。棧的插入和刪除等操作都在棧頂進行,它是先進后出的線性表。隊列的刪除操作在隊頭進行,而插入、查找等操作在隊尾進行,它是先進先出的線性表。與線性表一樣,棧和隊列有兩種存儲結構,順序存儲的棧稱為順序棧,鏈式存儲的棧稱為鏈棧。順序存儲的隊列稱為順序對列,鏈式存儲的隊列稱為鏈隊列。
為解決順序隊列中的假溢出問題,采用循環順序隊列,但出現隊空和隊滿的判斷條件相同的問題,判斷條件都是:front==rear。采用少用一個存儲單元來解決該問題。此時,隊滿的判斷條件是:(rear+1)%maxsize==front,判斷隊空的條件是:rear==front。
棧適合於具有先進后出特性的問題,如括號匹配、表達式求值等問題;隊列適合於具有先進先出特性的問題,如排隊等問題。
