寫在前面
整個項目都托管在了 Github 上:https://github.com/ikesnowy/Algorithms-4th-Edition-in-Csharp
這一節內容可能會用到的庫文件有 Generics,同樣在 Github 上可以找到。
善用 Ctrl + F 查找題目。
習題&題解
1.3.1
解答
首先是 FixedCapacityStackOfStrings 類,官方 JAVA 版本參考:FixedCapacityStackOfStrings.java
IsFull() 的實現比較簡單,判斷 N 與數組長度是否相等即可。
代碼
FixedCapacityStackOfStrings 類
using System; using System.Collections; using System.Collections.Generic; namespace _1._3._1 { class FixedCapacityStackOfStrings : IEnumerable<string> { private string[] a; private int N; /// <summary> /// 默認構造函數。 /// </summary> /// <param name="capacity">棧的大小。</param> public FixedCapacityStackOfStrings(int capacity) { this.a = new string[capacity]; this.N = 0; } /// <summary> /// 檢查棧是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.N == 0; } /// <summary> /// 檢查棧是否已滿。 /// </summary> /// <returns></returns> public bool IsFull() { return this.N == this.a.Length; } /// <summary> /// 將一個元素壓入棧中。 /// </summary> /// <param name="item">要壓入棧中的元素。</param> public void Push(string item) { this.a[N] = item; this.N++; } /// <summary> /// 從棧中彈出一個元素,返回被彈出的元素。 /// </summary> /// <returns></returns> public string Pop() { this.N--; return this.a[N]; } /// <summary> /// 返回棧頂元素(但不彈出它)。 /// </summary> /// <returns></returns> public string Peek() { return this.a[N - 1]; } public IEnumerator<string> GetEnumerator() { return new ReverseEnmerator(this.a); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class ReverseEnmerator : IEnumerator<string> { private int current; private string[] a; public ReverseEnmerator(string[] a) { this.current = a.Length; this.a = a; } string IEnumerator<string>.Current => a[current]; object IEnumerator.Current => a[current]; void IDisposable.Dispose() { this.current = -1; this.a = null; } bool IEnumerator.MoveNext() { if (this.current == 0) return false; this.current--; return true; } void IEnumerator.Reset() { this.current = a.Length; } } } }
1.3.2
解答
首先是 Stack<> 類的實現,官方 JAVA 版本參考:Stack.java
輸出內容:was best times of the was the it
代碼
Stack<> 類
using System; using System.Collections; using System.Collections.Generic; using System.Text; namespace Generics { public class Stack<Item> : IEnumerable<Item> { private Node<Item> first; private int count; /// <summary> /// 默認構造函數。 /// </summary> public Stack() { this.first = null; this.count = 0; } /// <summary> /// 復制構造函數。 /// </summary> /// <param name="s"></param> public Stack(Stack<Item> s) { if (s.first != null) { this.first = new Node<Item>(s.first); for (Node<Item> x = this.first; x.next != null; x = x.next) { x.next = new Node<Item>(x.next); } } } /// <summary> /// 檢查棧是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.first == null; } /// <summary> /// 返回棧內元素的數量。 /// </summary> /// <returns></returns> public int Size() { return this.count; } /// <summary> /// 將一個元素壓入棧中。 /// </summary> /// <param name="item">要壓入棧中的元素。</param> public void Push(Item item) { Node<Item> oldFirst = this.first; this.first = new Node<Item>(); this.first.item = item; this.first.next = oldFirst; this.count++; } /// <summary> /// 將一個元素從棧中彈出,返回彈出的元素。 /// </summary> /// <returns></returns> public Item Pop() { if (IsEmpty()) throw new InvalidOperationException("Stack Underflow"); Item item = this.first.item; this.first = this.first.next; this.count--; return item; } /// <summary> /// 返回棧頂元素(但不彈出它)。 /// </summary> /// <returns></returns> public Item Peek() { if (IsEmpty()) throw new InvalidOperationException("Stack Underflow"); return this.first.item; } /// <summary> /// 將兩個棧連接。 /// </summary> /// <param name="s1">第一個棧。</param> /// <param name="s2">第二個棧(將被刪除)。</param> /// <returns></returns> public static Stack<Item> Catenation(Stack<Item> s1, Stack<Item> s2) { if (s1.IsEmpty()) { s1.first = s2.first; s1.count = s2.count; } else { Node<Item> last = s1.first; while (last.next != null) { last = last.next; } last.next = s2.first; s1.count += s2.count; } s2 = null; return s1; } /// <summary> /// 創建棧的淺表副本。 /// </summary> /// <returns></returns> public Stack<Item> Copy() { Stack<Item> temp = new Stack<Item>(); temp.first = this.first; temp.count = this.count; return temp; } public override string ToString() { StringBuilder s = new StringBuilder(); foreach (Item n in this) { s.Append(n); s.Append(' '); } return s.ToString(); } public IEnumerator<Item> GetEnumerator() { return new StackEnumerator(this.first); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class StackEnumerator : IEnumerator<Item> { private Node<Item> current; private Node<Item> first; public StackEnumerator(Node<Item> first) { this.current = new Node<Item>(); this.current.next = first; this.first = this.current; } Item IEnumerator<Item>.Current => current.item; object IEnumerator.Current => current.item; void IDisposable.Dispose() { this.current = null; this.first = null; } bool IEnumerator.MoveNext() { if (this.current.next == null) return false; this.current = this.current.next; return true; } void IEnumerator.Reset() { this.current = this.first; } } } }
1.3.3
解答
這個問題的通用解法見習題 1.3.46 的解答。
第 2、6、7 個不可能產生,可以畫個棧模擬一下。
第 2 個
輸出數 棧內數
4 0~3
6 0~3 + 5
8 0~3 + 5 + 7
7 0~3 + 5
5 0~3
3 0~2
2 0~1
9 0~1
0 Error
第 6 個
輸出數 棧內數
0 null
4 1~3
6 1~3 + 5
5 1~3
3 1~2
8 1~2 + 7
1 Error
第 7 個
輸出數 棧內數
1 0
4 0 + 2~3
7 0 + 2~3 + 5~6
9 0 + 2~3 + 5~6 + 8
8 0 + 2~3 + 5~6
6 0 + 2~3 + 5
5 0 + 2~3
3 0 + 2
0 Error
1.3.4
解答
官方 JAVA 版本參考:Parentheses.java。
遇到左括號就入棧,遇到右括號就檢查是否和棧頂的左括號匹配,如果匹配則彈棧,否則返回 false。
結束時如果棧不為空則返回 false,否則返回 true。
代碼
using System; using Generics; namespace _1._3._4 { /* * 1.3.4 * * 編寫一個 Stack 的用例 Parentheses, * 從標准輸入中讀取一個文本流並使用棧判定其中的括號是否配對完整。 * 例如,對於 [()]{}{[()()]()} 程序應該打印 true, * 對於 [(]) 則打印 false。 * */ class Parentheses { static bool isBalanced(string input) { Stack<char> stack = new Stack<char>(); foreach (char i in input) { if (i == '(' || i == '[' || i == '{') stack.Push(i); else { if (stack.Peek() == '(' && i == ')') stack.Pop(); else if (stack.Peek() == '{' && i == '}') stack.Pop(); else if (stack.Peek() == '[' && i == ']') stack.Pop(); else return false; } } return stack.IsEmpty(); } static void Main(string[] args) { string input = "[()]{}{[()()]()}"; Console.WriteLine(isBalanced(input)); string input2 = "[(])"; Console.WriteLine(isBalanced(input2)); } } }
1.3.5
解答
實際上是用除二取余法求一個十進制數的二進制形式。
代碼
using System; using Generics; namespace _1._3._5 { /* * 1.3.5 * * 當 N 為 50 時下面這段代碼會打印什么? * 從較高的抽象層次描述給定正整數 N 時這段代碼的行為。 * */ class Program { //將十進制數 N 轉換為二進制數。 static void Main(string[] args) { int N = 50; Stack<int> stack = new Stack<int>(); while (N > 0) { stack.Push(N % 2); N = N / 2; } foreach (int d in stack) { Console.WriteLine(d); } Console.WriteLine(); } } }
1.3.6
解答
利用一個棧對隊列元素進行反序操作。
先把隊列中的元素全部入棧,再依次彈出並加入隊列中。
代碼
using System; using Generics; namespace _1._3._6 { /* * 1.3.6 * * 下面這段代碼對隊列 q 進行了什么操作? * */ class Program { //將隊列反序 static void Main(string[] args) { Queue<string> q = new Queue<string>(); q.Enqueue("first"); q.Enqueue("second"); q.Enqueue("third"); q.Enqueue("fourth"); Stack<string> stack = new Stack<string>(); while (!q.IsEmpty()) stack.Push(q.Dequeue()); while (!stack.IsEmpty()) q.Enqueue(stack.Pop()); Console.WriteLine(q.ToString()); } } }
1.3.7
解答
鏈表實現的話就是返回第一個結點 first 的 item 字段。
數組實現的話就是返回 first 對應的數組元素。
這里給出鏈表實現,完整實現見習題 1.3.2 的代碼。
代碼
/// <summary> /// 返回棧頂元素(但不彈出它)。 /// </summary> /// <returns></returns> public Item Peek() { if (IsEmpty()) throw new InvalidOperationException("Stack Underflow"); return this.first.item; }
1.3.8
解答
首先是 DoublingStackOfStrings 類,據我猜測應該是用數組實現的棧,擴容時長度增加一倍,縮短時長度減小一半。
官方 JAVA 代碼參考:FixedCapacityStackOfString.java。
代碼
DoublingStackOfStrings 類
using System; using System.Collections; using System.Collections.Generic; namespace _1._3._8 { class DoublingStackOfStrings : IEnumerable<string> { private string[] items; private int count; /// <summary> /// 新建一個字符串棧。 /// </summary> public DoublingStackOfStrings() { this.items = new string[2]; this.count = 0; } /// <summary> /// 檢查棧是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.count == 0; } /// <summary> /// 返回棧中字符串的數量。 /// </summary> /// <returns></returns> public int Size() { return this.count; } /// <summary> /// 向棧中壓入一個字符串。 /// </summary> /// <param name="s"></param> public void Push(string s) { if (this.count == this.items.Length) Resize(this.items.Length * 2); this.items[this.count] = s; this.count++; } /// <summary> /// 從棧中彈出一個字符串,返回被彈出的元素。 /// </summary> /// <returns></returns> public string Pop() { if (IsEmpty()) throw new InvalidOperationException("Stack underflow"); count--; //縮小長度 if (count > 0 && count <= items.Length / 4) Resize(items.Length / 2); return items[count]; } /// <summary> /// 返回棧頂元素(但不彈出它)。 /// </summary> /// <returns></returns> public string Peek() { if (IsEmpty()) throw new InvalidOperationException("Stack underflow"); return items[count - 1]; } /// <summary> /// 為棧重新分配空間,超出空間的元素將被舍棄。 /// </summary> /// <param name="capcity">重新分配的空間大小。</param> private void Resize(int capcity) { string[] temp = new string[capcity]; for (int i = 0; i < this.count; ++i) { temp[i] = items[i]; } items = temp; } public IEnumerator<string> GetEnumerator() { return new StackEnumerator(this.items); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class StackEnumerator : IEnumerator<string> { int current; string[] items; public StackEnumerator(string[] items) { this.items = items; current = -1; } string IEnumerator<string>.Current => this.items[this.current]; object IEnumerator.Current => this.items[this.current]; void IDisposable.Dispose() { this.items = null; this.current = -1; } bool IEnumerator.MoveNext() { if (this.current == items.Length - 1) return false; this.current++; return true; } void IEnumerator.Reset() { this.current = -1; } } } }
Program.cs
using System; namespace _1._3._8 { /* * 1.3.8 * * 給定以下輸入,給出 DoublingStackOfStrings 的數組的內容和大小。 * * it was - the best - of times - - - it was - the - - * */ class Program { static void Main(string[] args) { DoublingStackOfStrings stack = new DoublingStackOfStrings(); string[] input = "it was - the best - of times - - - it was - the - -".Split(' '); foreach (string n in input) { if (n == "-") stack.Pop(); else stack.Push(n); } foreach (string s in stack) { Console.Write(s + ' '); } Console.WriteLine($"\nStack Size: {stack.Size()}"); } } }
1.3.9
解答
在計算中序表達式算法的基礎上做修改。
壓入數字時將該數字所在的位置也一並壓入。
彈出數字進行運算時在位置靠前的數字前加上左括號。
A + B ) * C + D ) ) 為例。
A 壓入棧中並記錄位置 。
+ 壓入棧中。
B 壓入棧中並記錄位置。
) 計算,在 A 之前加入左括號,結果 E 壓入棧中,位置為 A 的位置。
* 壓入棧中。
C 壓入棧中並記錄位置。
+ 壓入棧中。
D 壓入棧中並記錄位置。
) 計算,在 C 之前加入左括號,結果 F 壓入棧中,位置為 C 的位置。
) 計算,在 E 之前加入左括號(也就是 A 之前),結果 G 壓入棧中,位置為 E 的位置。
代碼
using System; using Generics; namespace _1._3._9 { /* * 1.3.9 * * 編寫一段程序,從標准輸入得到一個缺少左括號的表達式並打印出補全括號之后的中序表達式。 * 例如,給定輸入: * 1 + 2 ) * 3 - 4 ) * 5 - 6 ) ) ) * 你的程序應該輸出: * ( ( 1 + 2 ) * ( ( 3 - 4 ) * ( 5 - 6 ) ) ) * */ class Program { //在計算中序表達式算法的基礎上做修改 //壓入數字時將該數字所在的位置也一並壓入 //彈出數字進行運算時在位置靠前的數字前加上左括號 //A + B ) * C + D ) ) 為例 //A 壓入棧中並記錄位置 //+ 壓入棧中 //B 壓入棧中並記錄位置 //) 計算,在 A 之前加入左括號,結果 E 壓入棧中,位置為 A 的位置 //* 壓入棧中 //C 壓入棧中並記錄位置 //+ 壓入棧中 //D 壓入棧中並記錄位置 //) 計算,在 C 之前加入左括號,結果 F 壓入棧中,位置為 C 的位置 //) 計算,在 E 之前加入左括號(也就是 A 之前),結果 G 壓入棧中,位置為 E 的位置。 static void Main(string[] args) { string input = "1 + 2 ) * 3 - 4 ) * 5 - 6 ) ) )"; Stack<char> operators = new Stack<char>(); Stack<Number> numbers = new Stack<Number>(); int[] leftBrackets = new int[input.Length]; for (int i = 0; i < input.Length; ++i) { if (input[i] == ' ') continue; else if (input[i] == '+' || input[i] == '-' || input[i] == '*' || input[i] == '/') { operators.Push(input[i]); } else if (input[i] == ')') { Number B = numbers.Pop(); Number A = numbers.Pop(); char operation = operators.Pop(); Number C = new Number(); C.Position = A.Position; leftBrackets[A.Position]++; switch (operation) { case '+': C.Value = A.Value + B.Value; break; case '-': C.Value = A.Value - B.Value; break; case '*': C.Value = A.Value * B.Value; break; case '/': C.Value = A.Value / B.Value; break; } numbers.Push(C); } else { Number num = new Number(); num.Position = i; num.Value = input[i] - '0'; numbers.Push(num); } } for (int i = 0; i < input.Length; ++i) { while (leftBrackets[i] != 0) { Console.Write("( "); leftBrackets[i]--; } Console.Write(input[i]); } } } struct Number { public int Value; public int Position; } }
1.3.10
解答
官方 JAVA 代碼:InfixToPostfix.java。
其實就是把右括號換成相應運算符
對於 (A + B),忽略左括號,數字直接輸出,運算符入棧,遇到右括號時再彈出
結果 A B +,變成后序表達式
代碼
using System; using Generics; namespace _1._3._10 { /* * 1.3.10 * * 編寫一個過濾器 InfixToPostfix, * 將算術表達式由中序表達式轉為后序表達式。 * */ class InfixToPostfix { //其實就是把右括號換成相應運算符 //對於 (A + B),忽略左括號,數字直接輸出,運算符入棧,遇到右括號時再彈出 //結果 A B +,變成后序表達式 static void Main(string[] args) { Stack<string> stack = new Stack<string>(); string[] input = "( 2 + ( ( 3 + 4 ) * ( 5 * 6 ) ) )".Split(' '); foreach (string n in input) { if (n == " ") continue; else if (n == "+" || n == "-" || n == "*" || n == "/") { stack.Push(n); } else if (n == ")") { Console.Write(stack.Pop() + " "); } else if (n == "(") { continue; } else { Console.Write(n + " "); } } Console.WriteLine(); } } }
1.3.11
解答
官方 JAVA 代碼:EvaluatePostfix.java。
遇到數字就入棧,遇到運算符就彈出兩個數字運算,再把結果入棧。
如果倒着讀取的話也可以用遞歸做,當作前序表達式計算即可。
代碼
using System; using Generics; namespace _1._3._11 { /* * 1.3.11 * * 編寫一段程序 EvaluatePostfix,從標准輸入中得到一個后序表達式,求值並打印結果 * (將上一題的程序中得到的輸出用管道傳遞給這一段程序可以得到和 Evaluate 相同的行為)。 * */ class EvaluatePostfix { static void Main(string[] args) { Stack<int> stack = new Stack<int>(); string[] input = "7 16 * 5 + 16 * 3 + 16 * 1 +".Split(' '); foreach (string n in input) { if (n == " ") { continue; } else if (n == "+") { stack.Push(stack.Pop() + stack.Pop()); } else if (n == "-") { int temp = stack.Pop(); stack.Push(stack.Pop() - temp); } else if (n == "*") { stack.Push(stack.Pop() * stack.Pop()); } else if (n == "/") { int temp = stack.Pop(); stack.Push(stack.Pop() / temp); } else { stack.Push(int.Parse(n)); } } Console.WriteLine(stack.Pop()); } } }
1.3.12
解答
先用 foreach 語句遍歷一遍棧,把所有元素都壓入一個臨時棧中。
此時臨時棧變成了源棧的一個倒序副本。
再將臨時棧中的元素依次壓入目標棧中,就得到了源棧的一個副本。
代碼
using System; using Generics; namespace _1._3._12 { /* * 1.3.12 * * 編寫一個可迭代的 Stack 用例,它含有一個靜態的 CopyTo() 方法, * 接受一個字符串的棧作為參數並返回該棧的一個副本。 * 注意:這種能力是迭代器價值的一個重要體現, * 因為有了它我們無需改變基本 API 就能實現這種功能。 * */ class Program { static void Main(string[] args) { Stack<string> src = new Stack<string>(); src.Push("first"); src.Push("second"); src.Push("third"); Stack<string> des = CopyTo(src); while (!des.IsEmpty()) { Console.WriteLine(des.Pop()); } } static Stack<string> CopyTo(Stack<string> src) { Stack<string> des = new Stack<string>(); Stack<string> temp = new Stack<string>(); foreach (string s in src) { temp.Push(s); } while (!temp.IsEmpty()) { des.Push(temp.Pop()); } return des; } } }
1.3.13
解答
除了第一個以外都不可能。
根據題意,0 一定是最先入列的。
那么根據隊列的特性,0 一定是最先出列的,因此除第一個以外其他幾個序列都不可能。
1.3.14
解答
對於 ResizingArrayQueueOfStrings 類,給出官方 JAVA 代碼參考:ResizingArrayQueue.java。
代碼
ResizingArrayQueueOfStrings 類
using System; using System.Collections; using System.Collections.Generic; namespace _1._3._14 { class ResizingArrayQueueOfStrings<Item> : IEnumerable<Item> { private Item[] q; private int count; private int first; private int last; public ResizingArrayQueueOfStrings() { this.q = new Item[2]; this.count = 0; this.first = 0; } public bool IsEmpty() { return this.count == 0; } public int Size() { return this.count; } private void Resize(int capacity) { if (capacity < 0) throw new ArgumentException("capacity should be above zero"); Item[] temp = new Item[capacity]; for (int i = 0; i < count; ++i) { temp[i] = this.q[(this.first + i) % this.q.Length]; } this.q = temp; this.first = 0; this.last = this.count; } public void Enqueue(Item item) { if (this.count == this.q.Length) { Resize(this.count * 2); } this.q[this.last] = item; this.last++; if (this.last == this.q.Length) this.last = 0; this.count++; } public Item Dequeue() { if (IsEmpty()) throw new InvalidOperationException("Queue underflow"); Item item = this.q[first]; this.q[first] = default(Item); this.count--; this.first++; if (this.first == this.q.Length) this.first = 0; if (this.count > 0 && this.count <= this.q.Length / 4) Resize(this.q.Length / 2); return item; } public Item Peek() { if (IsEmpty()) throw new InvalidOperationException("Queue underflow"); return this.q[first]; } public IEnumerator<Item> GetEnumerator() { return new QueueEnumerator(this.q, this.first, this.last); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class QueueEnumerator : IEnumerator<Item> { int current; int first; int last; Item[] q; public QueueEnumerator(Item[] q, int first, int last) { this.current = first - 1; this.first = first; this.last = last; this.q = q; } Item IEnumerator<Item>.Current => this.q[this.current]; object IEnumerator.Current => this.q[this.current]; void IDisposable.Dispose() { } bool IEnumerator.MoveNext() { if (this.current == this.last - 1) return false; this.current++; return true; } void IEnumerator.Reset() { this.current = this.first - 1; } } } }
Program.cs
using System; namespace _1._3._14 { /* * 1.3.14 * * 編寫一個類 ResizingArrayQueueOfStrings, * 使用定長數組實現隊列的抽象,然后擴展實現, * 使用調整數組的方法突破大小的限制。 * */ class Program { public static void Main(string[] args) { ResizingArrayQueueOfStrings<string> queue = new ResizingArrayQueueOfStrings<string>(); string[] input = "to be or not to - be - - that - - - is".Split(' '); foreach (string s in input) { if (!s.Equals("-")) queue.Enqueue(s); else if (!queue.IsEmpty()) Console.Write(queue.Dequeue() + ' '); } Console.WriteLine("(" + queue.Size() + " left on queue)"); } } }
1.3.15
解答
方法有很多,只要把所有輸入保存,之后算出倒數第 k 個是正數第幾個就可以了。
這里先全部入隊,之后算出是正數第幾個,再把前面的元素全部出隊,剩下的第一個就是要求的元素了。
代碼
using System; using Generics; namespace _1._3._15 { /* * 1.3.15 * * 編寫一個 Queue 的用例,接受一個命令行參數 k 並打印出標准輸入中的倒數第 k 個字符串 * (假設標准輸入中至少有 k 個字符串)。 * */ class Program { static void Main(string[] args) { Queue<string> queue = new Queue<string>(); string[] input = "1 2 3 4 5 6 7 8 9 10".Split(' '); int k = 4; foreach(string s in input) { queue.Enqueue(s); } int count = queue.Size() - k; for(int i = 0; i < count; ++i) { queue.Dequeue(); } Console.WriteLine(queue.Peek()); } } }
1.3.16
解答
在習題 1.2.19 里已經寫好了接受字符串作為參數構造函數(可以到 這個鏈接 里查看),
這里只要把所有字符串讀入並調用相應構造函數就可以了。
代碼
ReadDates()
/// <summary> /// 從標准輸入按行讀取所有日期,返回一個日期數組。 /// </summary> /// <returns></returns> public static Date[] ReadDates() { char[] split = new char[] { '\n' }; string[] input = Console.In.ReadToEnd().Split(split, StringSplitOptions.RemoveEmptyEntries); Date[] d = new Date[input.Length]; for (int i = 0; i < input.Length; ++i) { d[i] = new Date(input[i]); } return d; }
Program.cs
using System; namespace _1._3._16 { /* * 1.3.16 * * 使用 1.3.1.5 節中的 readInts() 作為模板為 Date 編寫一個靜態方法 readDates(), * 從標准輸入中讀取由練習 1.2.19 的表格指定的格式的多個日期並返回一個它們的數組。 * */ class Program { static void Main(string[] args) { //輸入結束后按 Ctrl + Z 標記結尾 //輸入格式:06/30/2017 //以回車分隔 Date[] date = Date.ReadDates(); foreach (Date d in date) { Console.WriteLine(d); } } } }
1.3.17
解答
和前一題類似,按行讀取輸入再調用相應構造函數就可以了。
代碼
ReadTransactions()
/// <summary> /// 從標准輸入中按行讀取所有交易信息,返回一個 Transaction 數組。 /// </summary> /// <returns></returns> public static Transaction[] ReadTransactions() { char[] split = new char[] { '\n' }; string[] input = Console.In.ReadToEnd().Split(split, StringSplitOptions.RemoveEmptyEntries); Transaction[] t = new Transaction[input.Length]; for (int i = 0; i < input.Length; ++i) { t[i] = new Transaction(input[i]); } return t; }
Program.cs
using System; namespace _1._3._17 { /* * 1.3.17 * * 為 Transaction 類完成練習 1.3.16 * */ class Program { static void Main(string[] args) { //用 Ctrl + Z 標記結束輸入 Transaction[] t = Transaction.ReadTransactions(); foreach (Transaction n in t) { Console.WriteLine(n.ToString()); } } } }
1.3.18
解答
刪除該結點的下一個結點。
如下圖,沒有任何結點指向 y 結點,失去了所有引用的 y 結點會被 GC 清理掉。
代碼
using System; using Generics; namespace _1._3._18 { /* * 1.3.18 * * 假設 x 是一條鏈表的某個結點且不是尾結點。 * 下面這條語句的效果是什么? * x.next = x.next.next; * */ class Program { //刪除 x 的后一個結點。 static void Main(string[] args) { Node<string> x = new Node<string>(); x.item = "first"; Node<string> y = new Node<string>(); y.item = "second"; x.next = y; Node<string> z = new Node<string>(); z.item = "third"; y.next = z; Console.WriteLine("x: " + x.item); Console.WriteLine("x.next: " + x.next.item); x.next = x.next.next; Console.WriteLine(); Console.WriteLine("x: " + x.item); Console.WriteLine("x.next: " + x.next.item); } } }
1.3.19
解答
建立一個結點引用 Cur,讓它移動到尾結點的前一個結點,讓那個結點的 next 變為 null。
代碼
using System; using Generics; namespace _1._3._19 { /* * 1.3.19 * * 給出一段代碼,刪除鏈表的尾結點,其中鏈表的首結點為 first。 * */ class Program { static void Main(string[] args) { Node<string> first = new Node<string>() { item = "first" }; Node<string> second = new Node<string>() { item = "second" }; Node<string> third = new Node<string>() { item = "third" }; first.next = second; second.next = third; third.next = null; Node<string> current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } DeleteLast(first); Console.WriteLine(); current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } Console.WriteLine(); } static void DeleteLast(Node<string> first) { Node<string> current = first; while (current.next.next != null) { current = current.next; } current.next = null; } } }
1.3.20
解答
和上一題類似,只不過這次讓 Cur 移動 k – 1 次即可。
代碼
/// <summary> /// 刪除指定位置的元素,返回該元素。 /// </summary> /// <param name="index">需要刪除元素的位置。</param> /// <returns></returns> public Item Delete(int index) { if (index >= this.count) { throw new IndexOutOfRangeException(); } Node<Item> front = this.first; Item temp = this.first.item; if (index == 0) { this.first = this.first.next; return temp; } for (int i = 1; i < index; ++i) { front = front.next; } temp = front.next.item; front.next = front.next.next; this.count--; return temp; }
1.3.21
解答
遍歷整條鏈表,方法和前兩題類似,用一個結點引用 Cur 去訪問就可以了。
代碼
using System; using Generics; namespace _1._3._21 { /* * 1.3.21 * * 編寫一個方法 find(),接受一條鏈表和一個字符串 key 作為參數。 * 如果鏈表中的某個結點的 item 域的值為 key,則方法返回 true,否則返回 false。 * */ class Program { static void Main(string[] args) { LinkedList<string> link = new LinkedList<string>(); link.Insert("first", 0); link.Insert("second", 1); link.Insert("third", 2); Console.WriteLine(Find(link, "second")); Console.WriteLine(Find(link, "fourth")); } static bool Find<Item>(LinkedList<Item> link, Item key) { foreach (Item i in link) { if (i.Equals(key)) { return true; } } return false; } } }
1.3.22
解答
在 x 之后插入 t,如下圖所示。
代碼
using System; using Generics; namespace _1._3._22 { /* * 1.3.22 * * 假設 x 是一條鏈表中的某個結點,下面這段代碼做了什么? * */ class Program { //將 t 插入到 x 之后 static void Main(string[] args) { Node<string> first = new Node<string>(); Node<string> second = new Node<string>(); Node<string> third = new Node<string>(); Node<string> fourth = new Node<string>(); first.item = "first"; second.item = "second"; third.item = "third"; fourth.item = "fourth"; first.next = second; second.next = third; third.next = fourth; fourth.next = null; Node<string> current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } Node<string> t = new Node<string>(); t.item = "t"; t.next = second.next; second.next = t; Console.WriteLine(); current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } } } }
1.3.23
解答
由於先后問題,y 在第一句代碼執行完畢之后無法訪問,t 的 next 會指向自己。
代碼
using System; using Generics; namespace _1._3._23 { /* * 1.3.23 * * 為什么下面這段代碼和上一題中的代碼效果不同? * */ class Program { //x.next = t x 的下一個是 t //t.next = x.next t 的下一個和 x 的下一個相同(也就是 t) //於是 t.next = t, 遍歷會出現死循環。 static void Main(string[] args) { Node<string> first = new Node<string>(); Node<string> second = new Node<string>(); Node<string> third = new Node<string>(); Node<string> fourth = new Node<string>(); first.item = "first"; second.item = "second"; third.item = "third"; fourth.item = "fourth"; first.next = second; second.next = third; third.next = fourth; fourth.next = null; Node<string> current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } Node<string> t = new Node<string>(); t.item = "t"; second.next = t; t.next = second.next; Console.WriteLine(); current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } } } }
1.3.24
解答
直接把該節點的 next 域設為 null,后續元素就會因無法訪問而被清理掉。
代碼
using System; using Generics; namespace _1._3._24 { /* * 1.3.24 * * 編寫一個方法 removeAfter(),接受一個鏈表結點作為參數並刪除該結點的后續結點。 * (如果參數結點的后續結點為空則什么也不做) * */ class Program { static void Main(string[] args) { Node<string> first = new Node<string>(); Node<string> second = new Node<string>(); Node<string> third = new Node<string>(); Node<string> fourth = new Node<string>(); first.item = "first"; second.item = "second"; third.item = "third"; fourth.item = "fourth"; first.next = second; second.next = third; third.next = fourth; fourth.next = null; Node<string> current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } RemoveAfter(second); Console.WriteLine(); current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } } static void RemoveAfter<Item>(Node<Item> i) { i.next = null; } } }
1.3.25
解答
見練習 1.3.22,加入一些對邊界情況的處理即可。
代碼
using System; using Generics; namespace _1._3._25 { /* * 1.3.25 * * 編寫一個方法 insertAfter(),接受兩個鏈表結點作為參數, * 將第二個結點插入鏈表並使之成為第一個結點的后續結點 * (如果兩個參數為空則什么也不做)。 * */ class Program { static void Main(string[] args) { Node<string> first = new Node<string>(); Node<string> second = new Node<string>(); Node<string> third = new Node<string>(); first.item = "first"; second.item = "second"; third.item = "third"; first.next = second; second.next = null; Node<string> current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } InsertAfter(second, third); Console.WriteLine(); current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } } static void InsertAfter<Item>(Node<Item> A, Node<Item> B) { if (A == null || B == null) return; B.next = A.next; A.next = B; } } }
1.3.26
解答
之前已經寫過了刪除指定結點(習題 1.3.20)和查找指定結點(習題 1.3.21),結合使用即可。
代碼
using System; using Generics; namespace _1._3._26 { /* * 1.3.26 * * 編寫一個方法 remove(),接受一條鏈表和一個字符串 key 作為參數, * 刪除鏈表中所有 item 域為 key 的結點。 * */ class Program { static void Main(string[] args) { LinkedList<string> link = new LinkedList<string>(); link.Insert("first", 0); link.Insert("second", 1); link.Insert("third", 2); link.Insert("third", 3); link.Insert("third", 4); Console.WriteLine(link); Remove(link, "third"); Console.WriteLine(link); } static void Remove(LinkedList<string> link, string key) { for (int i = 0; i < link.Size(); ++i) { if (link.Find(i) == key) { link.Delete(i); i--; } } } } }
1.3.27
解答
遍歷一遍即可。
代碼
using System; using Generics; namespace _1._3._27 { /* * 1.3.27 * * 編寫一個方法 max(),接受一條鏈表的首結點作為參數,返回鏈表中鍵最大的節點的值。 * 假設所有鍵均為正整數,如果鏈表為空則返回 0。 * */ class Program { static void Main(string[] args) { Node<int> first = new Node<int>(); Node<int> second = new Node<int>(); Node<int> third = new Node<int>(); Node<int> fourth = new Node<int>(); first.item = 1; second.item = 2; third.item = 3; fourth.item = 4; first.next = second; second.next = third; third.next = fourth; fourth.next = null; Console.WriteLine("Max:" + Max(first)); } static int Max(Node<int> first) { int max = 0; Node<int> current = first; while (current != null) { if (max < current.item) { max = current.item; } current = current.next; } return max; } } }
1.3.28
解答
其實鏈表本身就是一個遞歸結構,鏈表的定義可以用遞歸的方式表示:
鏈表 = 頭結點A + 鏈表B = 頭結點A + 頭結點B + 鏈表C……
所以 Max() 可以這么寫:
Max(Node<Item> Cur, int nowmax)
如果 Cur 為空,則直接返回 nowmax。
否則檢查 Cur 結點的值是否大於目前找到的最大值 nowmax。
如果不大於,繼續查找下一個結點,返回 Max(Cur.next, nowmax)
否則,把 nowmax 修改為當前結點的值,繼續查找,返回 Max(Cur.next, Cur.item)
代碼
using System; using Generics; namespace _1._3._28 { /* * 1.3.28 * * 用遞歸方法解答上一道練習。 * */ class Program { static void Main(string[] args) { Node<int> first = new Node<int>(); Node<int> second = new Node<int>(); Node<int> third = new Node<int>(); Node<int> fourth = new Node<int>(); first.item = 1; second.item = 2; third.item = 3; fourth.item = 4; first.next = second; second.next = third; third.next = fourth; fourth.next = null; Console.WriteLine("Max:" + Max(first)); } static int Max(Node<int> first, int max = 0) { if (first == null) return max; if (max < first.item) return Max(first.next, first.item); else return Max(first.next, max); } } }
1.3.29
解答
其實就是一個長這樣的鏈表:
顯然說 first 和最后一個節點的指針重復了,所以我們只需要保留 last 的指針就行了。
入隊(注意順序)
出隊
代碼
Queue.cs
using System; using System.Collections; using System.Collections.Generic; using System.Text; namespace _1._3._29 { public class Queue<Item> : IEnumerable<Item> { private Node<Item> last; private int count; /// <summary> /// 默認構造函數。 /// </summary> public Queue() { this.last = null; this.count = 0; } /// <summary> /// 檢查隊列是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.last == null; } /// <summary> /// 返回隊列中元素的數量。 /// </summary> /// <returns></returns> public int Size() { return this.count; } /// <summary> /// 返回隊列中的第一個元素(但不讓它出隊)。 /// </summary> /// <returns></returns> public Item Peek() { if (IsEmpty()) throw new InvalidOperationException("Queue underflow"); return this.last.next.item; } /// <summary> /// 將一個新元素加入隊列中。 /// </summary> /// <param name="item">要入隊的元素。</param> public void Enqueue(Item item) { Node<Item> oldLast = this.last; this.last = new Node<Item>(); this.last.item = item; this.last.next = this.last; if (oldLast != null) { this.last.next = oldLast.next; oldLast.next = this.last; } count++; } /// <summary> /// 將隊列中的第一個元素出隊並返回它。 /// </summary> /// <returns></returns> public Item Dequeue() { if (IsEmpty()) throw new InvalidOperationException("Queue underflow"); Item item = this.last.next.item; this.last.next = this.last.next.next; this.count--; if (IsEmpty()) this.last = null; return item; } public override string ToString() { StringBuilder s = new StringBuilder(); foreach (Item item in this) { s.Append(item); s.Append(" "); } return s.ToString(); } public IEnumerator<Item> GetEnumerator() { return new QueueEnumerator(this.last); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class QueueEnumerator : IEnumerator<Item> { private Node<Item> current; private Node<Item> first; public QueueEnumerator(Node<Item> last) { this.current = new Node<Item>(); this.current.next = last.next; this.first = this.current; } Item IEnumerator<Item>.Current => this.current.item; object IEnumerator.Current => this.current.item; void IDisposable.Dispose() { this.first = null; this.current = null; } bool IEnumerator.MoveNext() { if (this.current.next == first.next) return false; this.current = this.current.next; return true; } void IEnumerator.Reset() { this.current = this.first; } } } public class Node<Item> { public Item item; public Node<Item> next; } }
Program.cs
using System; namespace _1._3._29 { /* * 1.3.29 * * 用環形鏈表實現 Queue。 * 環形鏈表也是一條鏈表,只是沒有任何結點的鏈接為空,且只要鏈表非空則 last.next 的值為 first。 * 只能使用一個 Node 類型的實例變量(last)。 * */ class Program { static void Main(string[] args) { string input = "to be or not to - be - - that - - - is"; string[] s = input.Split(' '); Queue<string> queue = new Queue<string>(); foreach (string n in s) { if (!n.Equals("-")) queue.Enqueue(n); else if (!queue.IsEmpty()) Console.WriteLine(queue.Dequeue()); } Console.WriteLine($"({queue.Size()}) left on queue"); Console.WriteLine(queue); } } }
1.3.30
解答
書中給出了代碼,這里說一下遞歸的實現。
如果說一個鏈表除了第一個結點剩下的都已經反轉了,那么我們就只要把該結點插入到最后就行了(也就是原先的第二個結點之后)。
像這樣:
代碼
using System; using Generics; namespace _1._3._30 { /* * 1.3.30 * * 編寫一個函數,接受一條鏈表的首結點作為參數, * (破壞性地)將鏈表反轉並返回鏈表的首結點。 * */ class Program { static void Main(string[] args) { Node<string> first = new Node<string>(); Node<string> second = new Node<string>(); Node<string> third = new Node<string>(); Node<string> fourth = new Node<string>(); first.item = "first"; second.item = "second"; third.item = "third"; fourth.item = "fourth"; first.next = second; second.next = third; third.next = fourth; fourth.next = null; Node<string> current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } first = Reverse(first); Console.WriteLine(); current = first; while (current != null) { Console.Write(current.item + " "); current = current.next; } } //使用書中的遞歸方式實現 static Node<Item> Reverse<Item>(Node<Item> first) { if (first == null) return null; if (first.next == null) return first; Node<Item> second = first.next; Node<Item> rest = Reverse(second); second.next = first; first.next = null; return rest; } } }
1.3.31
解答
雙向鏈表的插入有順序,務必當心。
雙向鏈表長這樣(似乎有一種畫法是把空指針畫成“接地”的樣子):
刪除中間那個:
再插回去:
原則是不要讓有用的結點變得無法訪問。
代碼
DoubleNode<>
using System; using System.Collections; using System.Collections.Generic; using System.Text; namespace _1._3._31 { /* * 1.3.31 * * 實現一個嵌套類 DoubleNode 用來構造雙向鏈表, * 其中每個結點都含有一個指向前驅元素的應用和一項指向后續元素的引用(如果不存在則為 null)。 * 為以下任務實現若干靜態方法: * 在表頭插入結點。 * 在表尾插入結點。 * 從表頭刪除結點。 * 從表尾刪除結點。 * 在指定結點之前插入新結點。 * 在指定結點之后插入新結點。 * 刪除指定結點。 * */ public class DoubleLinkList<Item> : IEnumerable<Item> { private class DoubleNode<T> { public T item; public DoubleNode<T> prev; public DoubleNode<T> next; } DoubleNode<Item> first; DoubleNode<Item> last; int count; /// <summary> /// 建立一條雙向鏈表。 /// </summary> public DoubleLinkList() { this.first = null; this.last = null; this.count = 0; } /// <summary> /// 檢查鏈表是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return count == 0; } /// <summary> /// 返回鏈表中元素的數量。 /// </summary> /// <returns></returns> public int Size() { return this.count; } /// <summary> /// 在表頭插入一個元素。 /// </summary> /// <param name="item">要插入的元素。</param> public void InsertFront(Item item) { DoubleNode<Item> node = new DoubleNode<Item>() { item = item, next = this.first, prev = null }; if (this.first != null) { this.first.prev = node; } else { this.last = node; } this.first = node; this.count++; } /// <summary> /// 在表尾插入一個元素。 /// </summary> /// <param name="item">要插入表尾的元素。</param> public void InsertRear(Item item) { DoubleNode<Item> node = new DoubleNode<Item>() { item = item, next = null, prev = last }; if (this.last != null) { this.last.next = node; } else { this.first = node; } this.last = node; this.count++; } /// <summary> /// 檢索指定下標的元素。 /// </summary> /// <param name="index">要檢索的下標。</param> /// <returns></returns> public Item At(int index) { if (index >= count || index < 0) throw new IndexOutOfRangeException(); DoubleNode<Item> current = this.first; for (int i = 0; i < index; ++i) { current = current.next; } return current.item; } /// <summary> /// 返回指定下標的結點。 /// </summary> /// <param name="index">要查找的下標。</param> /// <returns></returns> private DoubleNode<Item> Find(int index) { if (index >= count || index < 0) throw new IndexOutOfRangeException(); DoubleNode<Item> current = this.first; for (int i = 0; i < index; ++i) { current = current.next; } return current; } /// <summary> /// 在指定位置之前插入一個元素。 /// </summary> /// <param name="item">要插入的元素。</param> /// <param name="index">插入位置的下標。</param> public void InsertBefore(Item item, int index) { if (index == 0) { InsertFront(item); return; } if (index >= count || index < 0) throw new IndexOutOfRangeException(); DoubleNode<Item> current = Find(index); DoubleNode<Item> node = new DoubleNode<Item>() { next = current, prev = current.prev, item = item }; current.prev.next = node; current.prev = node; this.count++; } /// <summary> /// 在指定位置之后插入一個元素。 /// </summary> /// <param name="item">要插入的元素。</param> /// <param name="index">查找元素的下標。</param> public void InsertAfter(Item item, int index) { if (index == count - 1) { InsertRear(item); return; } if (index >= count || index < 0) throw new IndexOutOfRangeException(); DoubleNode<Item> current = Find(index); DoubleNode<Item> node = new DoubleNode<Item>() { prev = current, next = current.next, item = item }; current.next.prev = node; current.next = node; this.count++; } /// <summary> /// 刪除表頭元素。 /// </summary> /// <returns></returns> public Item DeleteFront() { if (IsEmpty()) throw new InvalidOperationException("List underflow"); Item temp = this.first.item; this.first = this.first.next; this.count--; if (IsEmpty()) { this.last = null; } return temp; } /// <summary> /// 刪除表尾的元素。 /// </summary> /// <returns></returns> public Item DeleteRear() { if (IsEmpty()) throw new InvalidOperationException("List underflow"); Item temp = this.last.item; this.last = this.last.prev; this.count--; if (IsEmpty()) { this.first = null; } else { this.last.next = null; } return temp; } /// <summary> /// 刪除指定位置的元素。 /// </summary> /// <param name="index">要刪除元素的下標。</param> /// <returns></returns> public Item Delete(int index) { if (index < 0 || index >= this.count) throw new IndexOutOfRangeException(); if (index == 0) { return DeleteFront(); } if (index == count - 1) { return DeleteRear(); } DoubleNode<Item> current = Find(index); Item temp = current.item; current.prev.next = current.next; current.next.prev = current.prev; count--; return temp; } public override string ToString() { StringBuilder s = new StringBuilder(); foreach (Item i in this) { s.Append(i.ToString()); s.Append(" "); } return s.ToString(); } public IEnumerator<Item> GetEnumerator() { return new DoubleLinkListEnumerator(this.first); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class DoubleLinkListEnumerator : IEnumerator<Item> { DoubleNode<Item> current; DoubleNode<Item> first; public DoubleLinkListEnumerator(DoubleNode<Item> first) { this.current = new DoubleNode<Item>(); this.current.next = first; this.first = current; } Item IEnumerator<Item>.Current => current.item; object IEnumerator.Current => current.item; void IDisposable.Dispose() { this.current = null; this.first = null; } bool IEnumerator.MoveNext() { if (this.current.next == null) return false; this.current = this.current.next; return true; } void IEnumerator.Reset() { this.current = this.first; } } } }
Program.cs
using System; namespace _1._3._31 { class Program { static void Main(string[] args) { DoubleLinkList<string> linklist = new DoubleLinkList<string>(); linklist.InsertRear("fourth"); linklist.InsertFront("first"); linklist.InsertAfter("second", 0); linklist.InsertBefore("third", 2); Console.WriteLine(linklist); linklist.DeleteFront(); Console.WriteLine(linklist); linklist.DeleteRear(); Console.WriteLine(linklist); linklist.Delete(1); Console.WriteLine(linklist); Console.WriteLine(linklist.At(0)); } } }
1.3.32
解答
在隊列的基礎上增加一個在隊首插入元素的方法即可。
代碼
Steque.cs
using System; using System.Collections; using System.Collections.Generic; using System.Text; namespace _1._3._32 { //API: //public class Steque<Item> : Ienumerable<Item> // public Steque(); 默認構造函數。 // public bool IsEmpty(); 檢查 Steque 是否為空。 // public int Size(); 返回 Steque 中的元素數量。 // public void Push(Item item); 向 Steque 中壓入一個元素。 // public Item Pop(); 從 Steque 中彈出一個元素。 // public void Peek(); 返回棧頂元素(但不彈出它)。 // public void Enqueue(Item item); 將一個元素添加入 Steque 中。 public class Steque<Item> : IEnumerable<Item> { private Node<Item> first; private Node<Item> last; private int count; private class Node<T> { public T item; public Node<T> next; } /// <summary> /// 默認構造函數。 /// </summary> public Steque() { this.first = null; this.count = 0; } /// <summary> /// 檢查棧是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return count == 0; } /// <summary> /// 返回棧內元素的數量。 /// </summary> /// <returns></returns> public int Size() { return this.count; } /// <summary> /// 將一個元素壓入棧中。 /// </summary> /// <param name="item">要壓入棧中的元素。</param> public void Push(Item item) { Node<Item> oldFirst = first; this.first = new Node<Item>(); this.first.item = item; this.first.next = oldFirst; if (oldFirst == null) { this.last = this.first; } count++; } /// <summary> /// 將一個元素從棧中彈出,返回彈出的元素。 /// </summary> /// <returns></returns> public Item Pop() { if (IsEmpty()) throw new InvalidOperationException("Stack Underflow"); Item item = first.item; first = first.next; count--; if (count == 0) { this.last = null; } return item; } /// <summary> /// 將一個元素加入隊列中。 /// </summary> /// <param name="item">要入隊的元素。</param> public void Enqueue(Item item) { Node<Item> oldLast = this.last; this.last = new Node<Item>(); this.last.item = item; this.last.next = null; if (IsEmpty()) this.first = this.last; else oldLast.next = this.last; count++; } /// <summary> /// 返回棧頂元素(但不彈出它)。 /// </summary> /// <returns></returns> public Item Peek() { if (IsEmpty()) throw new InvalidOperationException("Stack Underflow"); return first.item; } public override string ToString() { StringBuilder s = new StringBuilder(); foreach (Item n in this) { s.Append(n); s.Append(' '); } return s.ToString(); } public IEnumerator<Item> GetEnumerator() { return new StackEnumerator(this.first); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class StackEnumerator : IEnumerator<Item> { private Node<Item> current; private Node<Item> first; public StackEnumerator(Node<Item> first) { this.current = new Node<Item>(); this.current.next = first; this.first = this.current; } Item IEnumerator<Item>.Current => current.item; object IEnumerator.Current => current.item; void IDisposable.Dispose() { this.current = null; this.first = null; } bool IEnumerator.MoveNext() { if (this.current.next == null) return false; this.current = this.current.next; return true; } void IEnumerator.Reset() { this.current = this.first; } } } }
Program.cs
using System; namespace _1._3._32 { /* * 1.3.32 * * Steque * 一個以棧為目標的隊列(或稱 steque), * 是一種支持 push、pop 和 enqueue 操作的數據類型。 * 為這種抽象數據類定義一份 API 並給出一份基於鏈表的實現。 * */ class Program { //見 Steque.cs static void Main(string[] args) { Steque<string> steque = new Steque<string>(); steque.Push("first"); steque.Push("second"); steque.Push("third"); steque.Enqueue("fourth"); Console.WriteLine(steque.ToString()); steque.Pop(); steque.Pop(); steque.Pop(); steque.Pop(); Console.WriteLine(steque.ToString()); steque.Enqueue("first"); steque.Push("zero"); Console.WriteLine(steque.ToString()); } } }
1.3.33
解答
動態數組這里要注意 first 不要小於零。
代碼
Deque 類
using System; using System.Collections; using System.Collections.Generic; namespace _1._3._33 { public class Deque<Item> : IEnumerable<Item> { private class DoubleNode<T> { public T item; public DoubleNode<T> next; public DoubleNode<T> prev; } DoubleNode<Item> first; DoubleNode<Item> last; int count; /// <summary> /// 默認構造函數,建立一個雙端隊列。 /// </summary> public Deque() { this.first = null; this.last = null; this.count = 0; } /// <summary> /// 檢查隊列是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.count == 0; } /// <summary> /// 返回隊列中元素的數量。 /// </summary> /// <returns></returns> public int Size() { return this.count; } /// <summary> /// 向左端添加一個元素。 /// </summary> /// <param name="item">要添加的元素。</param> public void PushLeft(Item item) { DoubleNode<Item> oldFirst = this.first; this.first = new DoubleNode<Item>() { item = item, prev = null, next = oldFirst }; if (oldFirst == null) { this.last = this.first; } else { oldFirst.prev = this.first; } this.count++; } /// <summary> /// 向右端添加一個元素。 /// </summary> /// <param name="item">要添加的元素。</param> public void PushRight(Item item) { DoubleNode<Item> oldLast = this.last; this.last = new DoubleNode<Item>() { item = item, prev = oldLast, next = null }; if (oldLast == null) { this.first = this.last; } else { oldLast.next = this.last; } this.count++; } /// <summary> /// 從右端刪除並返回一個元素。 /// </summary> /// <returns></returns> public Item PopRight() { if (IsEmpty()) { throw new InvalidOperationException(); } Item temp = this.last.item; this.last = this.last.prev; this.count--; if (this.last == null) { this.first = null; } else { this.last.next.item = default(Item); this.last.next = null; } return temp; } /// <summary> /// 從左端刪除並返回一個元素。 /// </summary> /// <returns></returns> public Item PopLeft() { if (IsEmpty()) { throw new InvalidOperationException(); } Item temp = this.first.item; this.first = this.first.next; this.count--; if (this.first == null) { this.last = null; } else { this.first.prev.item = default(Item); this.first.prev = null; } return temp; } public IEnumerator<Item> GetEnumerator() { return new DequeEnumerator(this.first); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class DequeEnumerator : IEnumerator<Item> { private DoubleNode<Item> current; private DoubleNode<Item> first; public DequeEnumerator(DoubleNode<Item> first) { this.current = new DoubleNode<Item>(); this.current.next = first; this.current.prev = null; this.first = this.current; } public Item Current => current.item; object IEnumerator.Current => current.item; public void Dispose() { this.current = null; this.first = null; } public bool MoveNext() { if (this.current.next == null) return false; this.current = this.current.next; return true; } public void Reset() { this.current = this.first; } } } }
ResizingArrayDeque 類
using System; using System.Collections; using System.Collections.Generic; namespace _1._3._33 { public class ResizingArrayDeque<Item> : IEnumerable<Item> { private Item[] deque; private int first; private int last; private int count; /// <summary> /// 默認構造函數,建立一個雙向隊列。 /// </summary> public ResizingArrayDeque() { this.deque = new Item[2]; this.first = 0; this.last = 0; this.count = 0; } /// <summary> /// 檢查隊列是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.count == 0; } /// <summary> /// 返回隊列中元素的數量。 /// </summary> /// <returns></returns> public int Size() { return this.count; } /// <summary> /// 為隊列重新分配空間。 /// </summary> /// <param name="capacity">需要重新分配的空間大小。</param> private void Resize(int capacity) { if (capacity <= 0) throw new ArgumentException(); Item[] temp = new Item[capacity]; for (int i = 0; i < count; ++i) { temp[i] = this.deque[(this.first + i) % this.deque.Length]; } this.deque = temp; this.first = 0; this.last = this.count; } /// <summary> /// 在隊列左側添加一個元素。 /// </summary> /// <param name="item">要添加的元素</param> public void PushLeft(Item item) { if (this.count == this.deque.Length) { Resize(2 * this.count); } this.first--; if (this.first < 0) { this.first += this.deque.Length; } this.deque[this.first] = item; this.count++; } public void PushRight (Item item) { if (this.count == this.deque.Length) { Resize(2 * this.count); } this.deque[this.last] = item; this.last = (this.last + 1) % this.deque.Length; this.count++; } public Item PopRight() { if (IsEmpty()) { throw new InvalidOperationException(); } this.last--; if (this.last < 0) { this.last += this.deque.Length; } Item temp = this.deque[this.last]; this.count--; if (this.count > 0 && this.count == deque.Length / 4) Resize(this.deque.Length / 2); return temp; } public Item PopLeft() { if (IsEmpty()) throw new ArgumentException(); Item temp = this.deque[this.first]; this.first = (this.first + 1) % this.deque.Length; this.count--; if (this.count > 0 && this.count == deque.Length / 4) { Resize(this.deque.Length / 2); } return temp; } public IEnumerator<Item> GetEnumerator() { return new ResizingDequeEnumerator(this.deque, this.first, this.count); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class ResizingDequeEnumerator : IEnumerator<Item> { private Item[] deque; private int current; private int first; private int count; public ResizingDequeEnumerator(Item[] deque, int first, int count) { this.deque = deque; this.first = first; this.count = count; this.current = -1; } Item IEnumerator<Item>.Current => this.deque[(this.first + this.current) % this.deque.Length]; object IEnumerator.Current => this.deque[(this.first + this.current) % this.deque.Length]; void IDisposable.Dispose() { this.deque = null; this.current = -1; } bool IEnumerator.MoveNext() { if (this.current == this.count - 1) { return false; } this.current++; return true; } void IEnumerator.Reset() { this.current = -1; } } } }
Program.cs
using System; namespace _1._3._33 { /* * 1.3.33 * * Deque。 * 一個雙向隊列(或稱 deque)和棧或隊列類似,但它同時支持在兩端添加或刪除元素。 * Deque 能夠存儲一組元素並支持下表中的 API: * * Deque() * 創建空雙向隊列。 * Bool isEmpty() * 雙向隊列是否為空。 * int size() * 雙向隊列中的元素數量。 * void pushLeft(Item item) * 向左端添加一個新元素。 * void pushRight(Item item) * 向右端添加一個新元素。 * Item popLeft() * 從左端刪除一個元素。 * Item popRight() * 從右端刪除一個元素。 * * 編寫一個使用雙向鏈表實現這份 API 的 Deque 類, * 以及一個使用動態數組調整實現這份 API 的 ResizingArrayDeque 類。 * */ class Program { static void Main(string[] args) { Deque<string> a = new Deque<string>(); ResizingArrayDeque<string> b = new ResizingArrayDeque<string>(); a.PushLeft("first"); b.PushLeft("first"); a.PushRight("second"); b.PushRight("second"); Display(a, b); a.PopLeft(); b.PopLeft(); Display(a, b); a.PopRight(); b.PopRight(); Display(a, b); } static void Display(Deque<string> a, ResizingArrayDeque<string> b) { foreach (string s in a) { Console.Write(s + " "); } Console.WriteLine(); foreach (string s in b) { Console.Write(s + " "); } Console.WriteLine(); Console.WriteLine(); } } }
1.3.34
解答
在初始化迭代器的時候隨機生成一個訪問序列,之后按照這個訪問序列進行迭代即可。
代碼
RandomBag.cs
using System; using System.Collections; using System.Collections.Generic; namespace _1._3._34 { public class RandomBag<Item> : IEnumerable<Item> { private Item[] bag; private int count; /// <summary> /// 建立一個隨機背包。 /// </summary> public RandomBag() { this.bag = new Item[2]; this.count = 0; } /// <summary> /// 檢查背包是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.count == 0; } /// <summary> /// 返回背包中元素的數量。 /// </summary> /// <returns></returns> public int Size() { return this.count; } /// <summary> /// 向背包中添加一個元素。 /// </summary> /// <param name="item">要向背包中添加的元素。</param> public void Add(Item item) { if (this.count == this.bag.Length) { Resize(this.count * 2); } this.bag[count] = item; count++; } /// <summary> /// 重新為背包分配內存空間。 /// </summary> /// <param name="capacity"></param> private void Resize(int capacity) { if (capacity <= 0) throw new ArgumentException(); Item[] temp = new Item[capacity]; for (int i = 0; i < this.count; ++i) { temp[i] = this.bag[i]; } this.bag = temp; } public IEnumerator<Item> GetEnumerator() { return new RandomBagEnumerator(this.bag, this.count); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class RandomBagEnumerator : IEnumerator<Item> { private Item[] bag; private int[] sequence; private int current; private int count; public RandomBagEnumerator(Item[] bag, int count) { this.bag = bag; this.current = -1; this.count = count; this.sequence = new int[count]; for (int i = 0; i < this.count; ++i) { this.sequence[i] = i; } Shuffle(sequence, DateTime.Now.Millisecond); } /// <summary> /// 隨機打亂數組。 /// </summary> /// <param name="a">需要打亂的數組。</param> /// <param name="seed">隨機種子值。</param> private void Shuffle(int[] a, int seed) { int N = a.Length; Random random = new Random(seed); for (int i = 0; i < N; ++i) { int r = i + random.Next(N - i); int temp = a[i]; a[i] = a[r]; a[r] = temp; } } Item IEnumerator<Item>.Current => this.bag[this.sequence[this.current]]; object IEnumerator.Current => this.bag[this.sequence[this.current]]; void IDisposable.Dispose() { this.bag = null; this.sequence = null; this.current = -1; } bool IEnumerator.MoveNext() { if (this.current == this.count - 1) return false; this.current++; return true; } void IEnumerator.Reset() { this.current = -1; } } } }
1.3.35
解答
事實上只需要在普通隊列的基礎上稍作修改就可以了。
出隊時先隨機選擇一個元素,之后讓它和最開始的元素做交換,之后正常出隊即可。
代碼
RandomQueue.cs
using System; namespace _1._3._35 { public class RandomQueue<Item> { private Item[] queue; private int count; /// <summary> /// 新建一個隨機隊列。 /// </summary> public RandomQueue() { this.queue = new Item[2]; this.count = 0; } /// <summary> /// 判斷隊列是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.count == 0; } /// <summary> /// 為隊列重新分配內存空間。 /// </summary> /// <param name="capacity"></param> private void Resize(int capacity) { if (capacity <= 0) { throw new ArgumentException(); } Item[] temp = new Item[capacity]; for (int i = 0; i < this.count; ++i) { temp[i] = this.queue[i]; } this.queue = temp; } /// <summary> /// 向隊列中添加一個元素。 /// </summary> /// <param name="item">要向隊列中添加的元素。</param> public void Enqueue(Item item) { if (this.queue.Length == this.count) { Resize(this.count * 2); } this.queue[this.count] = item; this.count++; } /// <summary> /// 從隊列中隨機刪除並返回一個元素。 /// </summary> /// <returns></returns> public Item Dequeue() { if (IsEmpty()) { throw new InvalidOperationException(); } Random random = new Random(DateTime.Now.Millisecond); int index = random.Next(this.count); Item temp = this.queue[index]; this.queue[index] = this.queue[this.count - 1]; this.queue[this.count - 1] = temp; this.count--; if (this.count < this.queue.Length / 4) { Resize(this.queue.Length / 2); } return temp; } /// <summary> /// 隨機返回一個隊列中的元素。 /// </summary> /// <returns></returns> public Item Sample() { if (IsEmpty()) { throw new InvalidOperationException(); } Random random = new Random(); int index = random.Next(this.count); return this.queue[index]; } } }
1.3.36
解答
實現方法和 1.3.34 類似,初始化迭代器的時候同時初始化一個隨機訪問序列。
代碼
RandomQueue.cs
using System; using System.Collections; using System.Collections.Generic; namespace _1._3._36 { public class RandomQueue<Item> : IEnumerable<Item> { private Item[] queue; private int count; /// <summary> /// 新建一個隨機隊列。 /// </summary> public RandomQueue() { this.queue = new Item[2]; this.count = 0; } /// <summary> /// 判斷隊列是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.count == 0; } /// <summary> /// 為隊列重新分配內存空間。 /// </summary> /// <param name="capacity"></param> private void Resize(int capacity) { if (capacity <= 0) { throw new ArgumentException(); } Item[] temp = new Item[capacity]; for (int i = 0; i < this.count; ++i) { temp[i] = this.queue[i]; } this.queue = temp; } /// <summary> /// 向隊列中添加一個元素。 /// </summary> /// <param name="item">要向隊列中添加的元素。</param> public void Enqueue(Item item) { if (this.queue.Length == this.count) { Resize(this.count * 2); } this.queue[this.count] = item; this.count++; } /// <summary> /// 從隊列中隨機刪除並返回一個元素。 /// </summary> /// <returns></returns> public Item Dequeue() { if (IsEmpty()) { throw new InvalidOperationException(); } Random random = new Random(DateTime.Now.Millisecond); int index = random.Next(this.count); Item temp = this.queue[index]; this.queue[index] = this.queue[this.count - 1]; this.queue[this.count - 1] = temp; this.count--; if (this.count < this.queue.Length / 4) { Resize(this.queue.Length / 2); } return temp; } /// <summary> /// 隨機返回一個隊列中的元素。 /// </summary> /// <returns></returns> public Item Sample() { if (IsEmpty()) { throw new InvalidOperationException(); } Random random = new Random(); int index = random.Next(this.count); return this.queue[index]; } public IEnumerator<Item> GetEnumerator() { return new RandomQueueEnumerator(this.queue, this.count); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class RandomQueueEnumerator : IEnumerator<Item> { private int current; private int count; private Item[] queue; private int[] sequence; public RandomQueueEnumerator(Item[] queue, int count) { this.count = count; this.queue = queue; this.current = -1; this.sequence = new int[this.count]; for (int i = 0; i < this.count; ++i) { this.sequence[i] = i; } Shuffle(this.sequence, DateTime.Now.Millisecond); } /// <summary> /// 隨機打亂數組。 /// </summary> /// <param name="a">需要打亂的數組。</param> /// <param name="seed">隨機種子值。</param> private void Shuffle(int[] a, int seed) { int N = a.Length; Random random = new Random(seed); for (int i = 0; i < N; ++i) { int r = i + random.Next(N - i); int temp = a[i]; a[i] = a[r]; a[r] = temp; } } Item IEnumerator<Item>.Current => this.queue[this.sequence[this.current]]; object IEnumerator.Current => this.queue[this.sequence[this.current]]; void IDisposable.Dispose() { this.current = -1; this.sequence = null; this.queue = null; } bool IEnumerator.MoveNext() { if (this.current == this.count - 1) return false; this.current++; return true; } void IEnumerator.Reset() { this.current = -1; } } } }
1.3.37
解答
也就是約瑟夫問題,官方給出的 JAVA 版答案:Josephus.java。
報數時將一個人出隊然后入隊來模擬一個環。
報到 M 個后將那個人出隊但不入隊(刪除)
隨后繼續循環。
代碼
using System; using Generics; namespace _1._3._37 { /* * 1.3.37 * * Josephus 問題。 * 在這個古老的問題中,N 個身陷絕境的人一致同意通過以下方式減少生存人數。 * 他們圍坐成一圈(位置記作 0 到 N-1)並從第一個人開始報數, * 報到 M 的人會被殺死,直到最后一個人留下來。 * 傳說中 Josephus 找到了不會被殺死的位置。 * 編寫一個 Queue 的用例 Josephus,從命令行接受 N 和 M 並打印出人們被殺死的順序 * (這也將顯示 Josephus 在圈中的位置)。 * */ class Program { static void Main(string[] args) { int numOfPeople = 7; int callForDeath = 2; Queue<int> queue = new Queue<int>(); for (int i = 0; i < numOfPeople; ++i) { queue.Enqueue(i); } while (!queue.IsEmpty()) { for (int i = 0; i < callForDeath - 1; ++i) { queue.Enqueue(queue.Dequeue()); } Console.Write(queue.Dequeue() + " "); } Console.WriteLine(); } } }
1.3.38
解答
這里采用“假刪除”的方式,對要刪除的元素不直接刪除而是打上標記,這樣就可以維持插入的順序。
代碼
數組實現:
using System; namespace _1._3._38 { class ArrayBasedGeneralizeQueue<Item> { private Item[] queue; private bool[] IsVisited; private int count; private int first; private int last; /// <summary> /// 建立一個隊列。 /// </summary> public ArrayBasedGeneralizeQueue() { this.queue = new Item[2]; this.IsVisited = new bool[2]; this.first = 0; this.last = 0; this.count = 0; } /// <summary> /// 檢查隊列是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.count == 0; } /// <summary> /// 為隊列重新分配空間。 /// </summary> /// <param name="capacity"></param> private void Resize(int capacity) { Item[] temp = new Item[capacity]; for (int i = 0; i < this.count; ++i) { temp[i] = this.queue[i]; } this.queue = temp; bool[] t = new bool[capacity]; for (int i = 0; i < this.count; ++i) { t[i] = this.IsVisited[i]; } this.IsVisited = t; } /// <summary> /// 向隊列中插入一個元素。 /// </summary> /// <param name="item">要插入隊列的元素。</param> public void Insert(Item item) { if (this.count == this.queue.Length) { Resize(this.queue.Length * 2); } this.queue[this.last] = item; this.IsVisited[this.last] = false; this.last++; this.count++; } /// <summary> /// 從隊列中刪除並返回第 k 個插入的元素。 /// </summary> /// <param name="k">要刪除元素的順序(從 1 開始)</param> /// <returns></returns> public Item Delete(int k) { if (IsEmpty()) { throw new InvalidOperationException(); } if (k > this.last || k < 0) { throw new ArgumentOutOfRangeException(); } if (IsVisited[k - 1] == true) { throw new ArgumentException("this node had been already deleted"); } Item temp = this.queue[k - 1]; this.IsVisited[k - 1] = true; this.count--; return temp; } } }
鏈表實現:
using System; namespace _1._3._38 { class LinkedListBasedGeneralizeQueue<Item> { private class Node<T> { public T item; public Node<T> next; public bool IsVisited; } private Node<Item> first; private Node<Item> last; private int count; /// <summary> /// 建立一個隊列。 /// </summary> public LinkedListBasedGeneralizeQueue() { this.first = null; this.last = null; this.count = 0; } /// <summary> /// 檢查數組是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.first == null; } /// <summary> /// 在隊尾插入元素。 /// </summary> /// <param name="item">需要插入的元素。</param> public void Insert(Item item) { Node<Item> oldLast = this.last; this.last = new Node<Item>() { item = item, IsVisited = false, next = null }; if (oldLast == null) { this.first = this.last; } else { oldLast.next = this.last; } count++; } /// <summary> /// 刪除第 k 個插入的結點 /// </summary> /// <param name="k">結點序號(從 1 開始)</param> /// <returns></returns> public Item Delete(int k) { if (k > this.count || k <= 0) { throw new ArgumentOutOfRangeException(); } k--; //找到目標結點 Node<Item> current = this.first; for (int i = 0; i < k; ++i) { current = current.next; } if (current.IsVisited == true) { throw new ArgumentException("this node had been already deleted"); } current.IsVisited = true; return current.item; } } }
1.3.39
解答
可以直接套用隊列的實現方式,在滿或空時拋出相應異常。
代碼
using System; namespace _1._3._39 { class RingBuffer<Item> { private Item[] buffer; private int count; private int first; //讀指針 private int last; //寫指針 /// <summary> /// 建立一個緩沖區。 /// </summary> /// <param name="N">緩沖區的大小。</param> public RingBuffer(int N) { this.buffer = new Item[N]; this.count = 0; this.first = 0; this.last = 0; } /// <summary> /// 檢查緩沖區是否已滿。 /// </summary> /// <returns></returns> public bool IsFull() { return this.count == this.buffer.Length; } /// <summary> /// 檢查緩沖區是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.count == 0; } /// <summary> /// 向緩沖區寫入數據。 /// </summary> /// <param name="item">要寫入的數據。</param> public void Write(Item item) { if (IsFull()) { throw new OutOfMemoryException("buffer is full"); } this.buffer[this.last] = item; this.last = (this.last + 1) % this.buffer.Length; this.count++; } /// <summary> /// 從緩沖區讀取一個數據。 /// </summary> /// <returns></returns> public Item Read() { if (IsEmpty()) { throw new InvalidOperationException(); } Item temp = this.buffer[this.first]; this.first = (this.first + 1) % this.buffer.Length; this.count--; return temp; } } }
1.3.40
解答
每次插入時都先搜索一遍鏈表,再判定相應動作。
代碼
using System; using System.Text; namespace _1._3._40 { class MoveToFront<Item> { private class Node<T> { public T item; public Node<T> next; } private Node<Item> first; private int count; /// <summary> /// 檢查編碼組是否為空。 /// </summary> /// <returns></returns> public bool IsEmpty() { return this.first == null; } /// <summary> /// 建立一個前移編碼組。 /// </summary> public MoveToFront() { this.first = null; this.count = 0; } /// <summary> /// 找到相應元素的前驅結點。 /// </summary> /// <param name="item">要尋找的元素。</param> /// <returns></returns> private Node<Item> Find(Item item) { if (IsEmpty()) { return null; } Node<Item> current = this.first; while (current.next != null) { if (current.next.item.Equals(item)) { return current; } current = current.next; } return null; } /// <summary> /// 前移編碼插入。 /// </summary> /// <param name="item">需要插入的元素。</param> public void Insert(Item item) { Node<Item> temp = Find(item); if (temp == null) { temp = new Node<Item>() { item = item, next = this.first }; this.first = temp; this.count++; } else if (temp != null && this.count != 1) { Node<Item> target = temp.next; temp.next = temp.next.next; target.next = this.first; this.first = target; } } /// <summary> /// 查看第一個元素。 /// </summary> /// <returns></returns> public Item Peek() { if (this.first == null) { throw new InvalidOperationException(); } return this.first.item; } public override string ToString() { StringBuilder s = new StringBuilder(); Node<Item> current = this.first; while (current != null) { s.Append(current.item.ToString()); s.Append(" "); current = current.next; } return s.ToString(); } } }
1.3.41
解答
可以按照書上的提示出隊再入隊,也可以直接用迭代器訪問一遍進行復制。
代碼
/// <summary> /// 復制構造函數。 /// </summary> /// <param name="r"></param> public Queue(Queue<Item> r) { foreach (Item i in r) { Enqueue(i); } }
1.3.42
解答
直接把鏈棧的整個鏈表復制一份即可。
代碼
/// <summary> /// 復制構造函數。 /// </summary> /// <param name="s"></param> public Stack(Stack<Item> s) { if (s.first != null) { this.first = new Node<Item>(s.first); for (Node<Item> x = this.first; x.next != null; x = x.next) { x.next = new Node<Item>(x.next); } } this.count = s.count; }
1.3.43
解答
C# 中可以用 Directory 類里面的幾個方法來獲得文件路徑和文件名。
代碼
using System; using System.IO; using System.Linq; namespace _1._3._43 { /* * 1.3.43 * * 文件列表。 * 文件夾就是一列文件和文件夾的列表。 * 編寫一個程序,從命令行接受一個文件夾名作為參數, * 打印出該文件夾下的所有文件並用遞歸的方式在所有子文件夾的名下(縮進)列出其下的所有文件。 * */ class Program { static void Main(string[] args) { //獲取當前目錄 string path = Directory.GetCurrentDirectory(); path = Directory.GetParent(path).FullName; path = Directory.GetParent(path).FullName; //獲取文件 Console.WriteLine(path + "中的所有文件"); Search(path, 0); } static void Search(string path, int tabs) { string[] dirs = Directory.GetDirectories(path); string[] files = Directory.GetFiles(path); foreach (string p in dirs) { for (int i = 0; i < tabs; ++i) { Console.Write(" "); } Console.WriteLine(p.Split('\\').Last()); Search(p, tabs + 1); } foreach (string f in files) { for (int i = 0; i < tabs; ++i) { Console.Write(" "); } Console.WriteLine(f.Split('\\').Last()); } } } }
1.3.44
解答
這里我們使用兩個棧來模擬緩沖區。
向左/向右移動 = 從左/右棧彈出相應數量的元素並壓入另外一個棧。
插入/刪除 = 左棧壓入/彈出一個元素。
字符數量 = 左棧數量 + 右棧數量。
代碼
using Generics; namespace _1._3._44 { class Buffer { private Stack<char> leftside; private Stack<char> rightside; /// <summary> /// 建立一個文本緩沖區。 /// </summary> public Buffer() { this.leftside = new Stack<char>(); this.rightside = new Stack<char>(); } /// <summary> /// 在光標位置插入字符 c。 /// </summary> /// <param name="c">要插入的字符。</param> public void Insert(char c) { this.leftside.Push(c); } /// <summary> /// 刪除並返回光標位置的字符。 /// </summary> /// <returns></returns> public char Delete() { return this.leftside.Pop(); } /// <summary> /// 將光標向左移動 k 個位置。 /// </summary> /// <param name="k">光標移動的距離。</param> public void Left(int k) { for (int i = 0; i < k; ++i) { this.rightside.Push(this.leftside.Pop()); } } /// <summary> /// 將光標向右移動 k 個位置。 /// </summary> /// <param name="k">光標移動的距離。</param> public void Right(int k) { for (int i = 0; i < k; ++i) { this.leftside.Push(this.rightside.Pop()); } } /// <summary> /// 返回緩沖區中的字符數量。 /// </summary> /// <returns></returns> public int Size() { return this.leftside.Size() + this.rightside.Size(); } /// <summary> /// 將緩沖區的內容輸出,這將使光標重置到最左端。 /// </summary> /// <returns></returns> public string Getstring() { while (!leftside.IsEmpty()) { this.rightside.Push(this.leftside.Pop()); } return rightside.ToString(); } } }
1.3.45
解答
書上已經給出了思路,簡單說明一下。
第一問是給定輸入判斷是否會下溢出,只要記錄棧中元素的數量即可,一旦為負數則返回 true。
第二問是給定輸出判斷是否可能。
對於輸出序列中的每一個數,如果棧頂為空或者棧頂數字小於當前輸出序列的數,那么就從輸入序列中輸入數字,直到棧頂數字和當前輸出序列中的數字相等。
如果當前輸出序列中的數字和棧頂元素相等,從棧中彈出相應元素。
最后如果棧為空則可能,否則不可能。
可以結合習題 1.3.3 的解答查看。
通用解法見下一題。
代碼
using System; using Generics; namespace _1._3._45 { /* * 1.3.45 * * 棧的可生成性。 * 假設我們的棧測試用例會進行一系列的入棧和出棧操作, * 序列中的整數 0, 1, ... , N - 1 (按此先后順序排列)表示入棧操作,N個減號表示出棧操作。 * 設計一個算法,判定給定的混合序列是否會使數組向下溢出 * (你使用的空間量與 N 無關,即不能用某種數據結構存儲所有整數)。 * 設計一個線性時間算法判定我們的測試用例能否產生某個給定的排列 * (這取決於出棧操作指令的出現位置)。 * */ class Program { static void Main(string[] args) { //給定輸入序列,判斷是否會出現下溢出。 string input = "- 0 1 2 3 4 5 6 7 8 9 - - - - - - - - -"; Console.WriteLine(IsUnderflow(input.Split(' ')));//True input = "0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 -"; Console.WriteLine(IsUnderflow(input.Split(' ')));//False //給定輸出序列,判定是否可能。 int[] output = { 4, 3, 2, 1, 0, 9, 8, 7, 6, 5 }; Console.WriteLine(IsOutputPossible(output));//True output = new int[]{ 4, 6, 8, 7, 5, 3, 2, 9, 0, 1}; Console.WriteLine(IsOutputPossible(output));//False } /// <summary> /// 判斷是否會出現下溢出。 /// </summary> /// <param name="input">輸入序列。</param> /// <returns></returns> static bool IsUnderflow(string[] input) { //記錄棧中元素數量,如果元素數量小於 0 則會出現下溢出。 int count = 0; foreach (string s in input) { if (count < 0) { return true; } if (s.Equals("-")) { count--; } else { count++; } } return false; } /// <summary> /// 判斷輸出序列是否正確。 /// </summary> /// <param name="output">輸出序列。</param> /// <returns></returns> static bool IsOutputPossible(int[] output) { int input = 0; int N = output.Length; Stack<int> stack = new Stack<int>(); foreach (int i in output) { //如果棧為空,則從輸入序列中壓入一個數。 if (stack.IsEmpty()) { stack.Push(input); input++; } //如果輸入序列中的所有數都已經入棧過了,跳出循環。 if (input == N && stack.Peek() != i) { break; } //如果輸出序列的下一個數不等於棧頂的數,那么就從輸入序列中壓入一個數。 while (stack.Peek() != i && input < N) { stack.Push(input); input++; } //如果棧頂的數等於輸出的數,彈出它。 if (stack.Peek() == i) { stack.Pop(); } } return stack.IsEmpty(); } } }
1.3.46
解答
這道題的解答參考了這篇博文:http://ceeji.net/blog/forbidden-triple-for-stack-generability/。
顯然書中的解答已經十分明確,這里簡單說明一下:
首先有結論:對於棧頂元素 Sn,棧中所有小於 Sn 的值都以遞減形式保存(已經輸出的不算)。
表現在輸出序列中,Sn 輸出之后,如果有小於 Sn 的值輸出,其順序必定是遞減的。
例如序列 4 3 2 1 0 9 8 7 6 5
4 輸出之后,3 2 1 0 遞減輸出;9 輸出之后,8 7 6 5 遞減輸出。
依次驗證其中的每個值都能滿足結論。
而對於序列 4 6 8 7 5 3 2 9 0 1
對於 4,之后的 3 2 1 0 並不是以遞減順序輸出的,因此這個序列是不合法的。
1.3.47
解答
這里用的都是鏈式結構,頭尾相接即可。
代碼
Queue:
/// <summary> /// 在當前隊列之后附加一個隊列。 /// </summary> /// <param name="q1">需要被附加的隊列。</param> /// <param name="q2">需要附加的隊列(將被刪除)。</param> public static Queue<Item> Catenation(Queue<Item> q1, Queue<Item> q2) { if (q1.IsEmpty()) { q1.first = q2.first; q1.last = q2.last; q1.count = q2.count; } else { q1.last.next = q2.first; q1.last = q2.last; q1.count += q2.count; } q2 = null; return q1; }
Stack:
/// <summary> /// 將兩個棧連接。 /// </summary> /// <param name="s1">第一個棧。</param> /// <param name="s2">第二個棧(將被刪除)。</param> /// <returns></returns> public static Stack<Item> Catenation(Stack<Item> s1, Stack<Item> s2) { if (s1.IsEmpty()) { s1.first = s2.first; s1.count = s2.count; } else { Node<Item> last = s1.first; while (last.next != null) { last = last.next; } last.next = s2.first; s1.count += s2.count; } s2 = null; return s1; }
Steque:
/// <summary> /// 將兩個 Steque 連接。 /// </summary> /// <param name="s1">第一個 Steque </param> /// <param name="s2">第二個 Steque (將被刪除)</param> /// <returns></returns> public static Steque<Item> Catenation(Steque<Item> s1, Steque<Item> s2) { if (s1.IsEmpty()) { s1.first = s2.first; s1.last = s2.last; s1.count = s2.count; } else { s1.last.next = s2.first; s1.count += s2.count; } s2 = null; return s1; }
1.3.48
解答
按照雙向隊列原本的操作就可以實現,需要維護兩個棧的長度以防越界。(左側棧彈出了右側棧棧底的內容)
代碼
using System; using System.Collections; using System.Collections.Generic; namespace _1._3._48 { public class DeStack<Item> : IEnumerable<Item> { private class DoubleNode<T> { public T item; public DoubleNode<T> next; public DoubleNode<T> prev; } DoubleNode<Item> first; DoubleNode<Item> last; int leftcount; int rightcount; /// <summary> /// 默認構造函數,建立一個雙端棧。 /// </summary> public DeStack() { this.first = null; this.last = null; this.leftcount = 0; this.rightcount = 0; } /// <summary> /// 檢查左側棧是否為空。 /// </summary> /// <returns></returns> public bool IsLeftEmpty() { return this.leftcount == 0; } /// <summary> /// 檢查右側棧是否為空。 /// </summary> /// <returns></returns> public bool IsRightEmpty() { return this.rightcount == 0; } /// <summary> /// 返回左側棧中元素的數量。 /// </summary> /// <returns></returns> public int LeftSize() { return this.leftcount; } /// <summary> /// 返回右側棧中元素的數量。 /// </summary> /// <returns></returns> public int RightSize() { return this.rightcount; } /// <summary> /// 向左端添加一個元素。 /// </summary> /// <param name="item">要添加的元素。</param> public void PushLeft(Item item) { DoubleNode<Item> oldFirst = this.first; this.first = new DoubleNode<Item>() { item = item, prev = null, next = oldFirst }; if (oldFirst == null) { this.last = this.first; } else { oldFirst.prev = this.first; } this.leftcount++; } /// <summary> /// 向右端添加一個元素。 /// </summary> /// <param name="item">要添加的元素。</param> public void PushRight(Item item) { DoubleNode<Item> oldLast = this.last; this.last = new DoubleNode<Item>() { item = item, prev = oldLast, next = null }; if (oldLast == null) { this.first = this.last; } else { oldLast.next = this.last; } this.rightcount++; } /// <summary> /// 從右端刪除並返回一個元素。 /// </summary> /// <returns></returns> public Item PopRight() { if (IsRightEmpty()) { throw new InvalidOperationException(); } Item temp = this.last.item; this.last = this.last.prev; this.rightcount--; if (this.last == null) { this.first = null; } else { this.last.next.item = default(Item); this.last.next = null; } return temp; } /// <summary> /// 從左端刪除並返回一個元素。 /// </summary> /// <returns></returns> public Item PopLeft() { if (IsLeftEmpty()) { throw new InvalidOperationException(); } Item temp = this.first.item; this.first = this.first.next; this.leftcount--; if (this.first == null) { this.last = null; } else { this.first.prev.item = default(Item); this.first.prev = null; } return temp; } public IEnumerator<Item> GetEnumerator() { return new DequeEnumerator(this.first); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class DequeEnumerator : IEnumerator<Item> { private DoubleNode<Item> current; private DoubleNode<Item> first; public DequeEnumerator(DoubleNode<Item> first) { this.current = new DoubleNode<Item>(); this.current.next = first; this.current.prev = null; this.first = this.current; } public Item Current => current.item; object IEnumerator.Current => current.item; public void Dispose() { this.current = null; this.first = null; } public bool MoveNext() { if (this.current.next == null) return false; this.current = this.current.next; return true; } public void Reset() { this.current = this.first; } } } }
1.3.49
解答
用六個棧即可實現,具體請查看我的這篇博文(有點復雜):用 6 個棧實現一個 O(1) 隊列。
1.3.50
解答
初始化迭代器的時候記錄棧已經進行過的 Pop 和 Push 數,迭代的時候檢查這兩個值是否改變,一旦改變就拋出異常。
代碼
修改后的迭代器代碼:
private class StackEnumerator : IEnumerator<Item> { private Stack<Item> s; private int popcount; private int pushcount; private Node<Item> current; public StackEnumerator(Stack<Item> s) { this.s = s; this.current = s.first; this.popcount = s.popcount; this.pushcount = s.pushcount; } Item IEnumerator<Item>.Current => current.item; object IEnumerator.Current => current.item; void IDisposable.Dispose() { this.current = null; this.s = null; } bool IEnumerator.MoveNext() { if (s.popcount != this.popcount || s.pushcount != this.pushcount) throw new InvalidOperationException("Stack has been modified"); if (this.current.next == null) return false; this.current = this.current.next; return true; } void IEnumerator.Reset() { this.current = this.s.first; } }
: #0000ff;">return this.rightcount; } /// <summary> /// 向左端添加一個元素。 /// </summary> /// <param name="item">要添加的元素。</param> public void PushLeft(Item item) { DoubleNode<Item> oldFirst = this.first; this.first = new DoubleNode<Item>() { item = item, prev = null, next = oldFirst }; if (oldFirst == null) { this.last = this.first; } else { oldFirst.prev = this.first; } this.leftcount++; } /// <summary> /// 向右端添加一個元素。 /// </summary> /// <param name="item">要添加的元素。</param> public void PushRight(Item item) { DoubleNode<Item> oldLast = this.last; this.last = new DoubleNode<Item>() { item = item, prev = oldLast, next = null }; if (oldLast == null) { this.first = this.last; } else { oldLast.next = this.last; } this.rightcount++; } /// <summary> /// 從右端刪除並返回一個元素。 /// </summary> /// <returns></returns> public Item PopRight() { if (IsRightEmpty()) { throw new InvalidOperationException(); } Item temp = this.last.item; this.last = this.last.prev; this.rightcount--; if (this.last == null) { this.first = null; } else { this.last.next.item = default(Item); this.last.next = null; } return temp; } /// <summary> /// 從左端刪除並返回一個元素。 /// </summary> /// <returns></returns> public Item PopLeft() { if (IsLeftEmpty()) { throw new InvalidOperationException(); } Item temp = this.first.item; this.first = this.first.next; this.leftcount--; if (this.first == null) { this.last = null; } else { this.first.prev.item = default(Item); this.first.prev = null; } return temp; } public IEnumerator<Item> GetEnumerator() { return new DequeEnumerator(this.first); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class DequeEnumerator : IEnumerator<Item> { private DoubleNode<Item> current; private DoubleNode<Item> first; public DequeEnumerator(DoubleNode<Item> first) { this.current = new DoubleNode<Item>(); this.current.next = first; this.current.prev = null; this.first = this.current; } public Item Current => current.item; object IEnumerator.Current => current.item; public void Dispose() { this.current = null; this.first = null; } public bool MoveNext() { if (this.current.next == null) return false; this.current = this.current.next; return true; } public void Reset() { this.current = this.first; } } } }
1.3.49
題目
棧與隊列。
用有限個棧實現一個隊列,
保證每個隊列操作(在最壞情況下)都只需要常數次的棧操作。
解答
用六個棧即可實現,具體請查看我的這篇博文(有點復雜):用 6 個棧實現一個 O(1) 隊列。
1.3.50
題目
快速出錯的迭代器。
修改 Stack 的迭代器代碼,確保一旦用例在迭代器中(通過 push() 或 pop() 操作)修改集合數據就拋出一個 java.util.ConcurrentModificationException 異常。
解答
初始化迭代器的時候記錄棧已經進行過的 Pop 和 Push 數,迭代的時候檢查這兩個值是否改變,一旦改變就拋出異常。
代碼
修改后的迭代器代碼:
private class StackEnumerator : IEnumerator<Item> { private Stack<Item> s; private int popcount; private int pushcount; private Node<Item> current; public StackEnumerator(Stack<Item> s) { this.s = s; this.current = s.first; this.popcount = s.popcount; this.pushcount = s.pushcount; } Item IEnumerator<Item>.Current => current.item; object IEnumerator.Current => current.item; void IDisposable.Dispose() { this.current = null; this.s = null; } bool IEnumerator.MoveNext() { if (s.popcount != this.popcount || s.pushcount != this.pushcount) throw new InvalidOperationException("Stack has been modified"); if (this.current.next == null) return false; this.current = this.current.next; return true; } void IEnumerator.Reset() { this.current = this.s.first; } }