寫在前面
整個項目都托管在了 Github 上:https://github.com/ikesnowy/Algorithms-4th-Edition-in-Csharp
查找更為方便的版本見:https://alg4.ikesnowy.com
這一節內容可能會用到的庫文件有 Merge,同樣在 Github 上可以找到。
善用 Ctrl + F 查找題目。
習題&題解
2.2.1
解答
2.2.2
解答
2.2.3
解答
2.2.4
解答
是的,必須要兩個子數組都有序時歸並才能得到正確結果。
如果說數組不有序的話,那么最后只能得到兩個數組的混合。
合並后的數組中,屬於原有數組的元素的相對順序不會被改變。
例如子數組 1 3 1 和 2 8 5 原地歸並。
結果是 1 2 3 1 8 5,其中 1 3 1 和 2 8 5 的相對順序不變。
2.2.5
解答
每次歸並子數組的大小和順序如下:
自頂向下
2, 3, 2, 5, 2, 3, 2, 5, 10, 2, 3, 2, 5, 2, 3, 2, 5, 10, 20, 2, 3, 2, 5, 2, 3, 2, 5, 10, 2, 3, 2, 5, 2, 2, 4, 9, 19, 39
自底向上
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 8, 8, 8, 8, 7, 16, 16, 32, 39
2.2.6
解答
灰色是上限,藍點是自頂向下,紅點是自底向上。
由於兩種排序訪問數組的次數是一樣的,因此藍點和紅點重合。
代碼
給出繪圖部分的代碼:
using System; using System.Windows.Forms; using System.Drawing; using Merge; namespace _2._2._6 { /* * 2.2.6 * * 編寫一個程序來計算自頂向下和自底向上的歸並排序訪問數組的准確次數。 * 使用這個程序將 N=1 至 512 的結果繪成曲線圖, * 並將其和上限 6NlgN 相比較。 * */ static class Program { /// <summary> /// 應用程序的主入口點。 /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Compute(); Application.Run(new Form1()); } static void Compute() { MergeSort mergeSort = new MergeSort(); MergeSortBU mergeSortBU = new MergeSortBU(); int[] mergeResult = new int[10]; int[] mergeResultBU = new int[10]; int[] upperBound = new int[10]; // 進行計算 int dataSize = 1; for (int i = 0; i < 10; i++) { int[] dataMerge = SortCompare.GetRandomArrayInt(dataSize); int[] dataMergeBU = new int[dataSize]; dataMerge.CopyTo(dataMergeBU, 0); mergeSort.ClearArrayVisitCount(); mergeSortBU.ClearArrayVisitCount(); mergeSort.Sort(dataMerge); mergeSortBU.Sort(dataMergeBU); mergeResult[i] = mergeSort.GetArrayVisitCount(); mergeResultBU[i] = mergeSortBU.GetArrayVisitCount(); upperBound[i] = (int)(6 * dataSize * Math.Log(dataSize, 2)); dataSize *= 2; } // 繪圖 Form2 plot = new Form2(); plot.Show(); Graphics graphics = plot.CreateGraphics(); // 獲得繪圖區矩形。 RectangleF rect = plot.ClientRectangle; float unitX = rect.Width / 10; float unitY = rect.Width / 10; // 添加 10% 邊距作為文字區域。 RectangleF center = new RectangleF (rect.X + unitX, rect.Y + unitY, rect.Width - 2 * unitX, rect.Height - 2 * unitY); // 繪制坐標系。 graphics.DrawLine(Pens.Black, center.Left, center.Top, center.Left, center.Bottom); graphics.DrawLine(Pens.Black, center.Left, center.Bottom, center.Right, center.Bottom); graphics.DrawString("28000", plot.Font, Brushes.Black, rect.Location); graphics.DrawString("1024", plot.Font, Brushes.Black, center.Right, center.Bottom); graphics.DrawString("0", plot.Font, Brushes.Black, rect.Left, center.Bottom); // 初始化點。 PointF[] grayPoints = new PointF[10]; // 上限 PointF[] redPoints = new PointF[10]; // 自頂向下 PointF[] bluePoints = new PointF[10]; // 自底向上 unitX = center.Width / 11.0f; unitY = center.Height / 28000.0f; for (int i = 0; i < 10; i++) { grayPoints[i] = new PointF(center.Left + unitX * (i + 1), center.Bottom - (upperBound[i] * unitY) - 10); redPoints[i] = new PointF(center.Left + unitX * (i + 1), center.Bottom - (mergeResult[i] * unitY) - 10); bluePoints[i] = new PointF(center.Left + unitX * (i + 1), center.Bottom - (mergeResultBU[i] * unitY) - 10); } // 繪制點。 for (int i = 0; i < 10; i++) { graphics.FillEllipse(Brushes.Gray, new RectangleF(grayPoints[i], new SizeF(10, 10))); graphics.FillEllipse(Brushes.Red, new RectangleF(redPoints[i], new SizeF(10, 10))); graphics.FillEllipse(Brushes.Blue, new RectangleF(bluePoints[i], new Size(10, 10))); } graphics.Dispose(); } } }
2.2.7
解答
根據書本給出的命題 G 和命題 H(中文版 P173/176,英文版 P275/279),
比較次數的下限 C(N) = 1/2 * NlgN
N 和 lgN 都是單調遞增且大於零的(N>1),因此 C(N) 也是單調遞增的
2.2.8
解答
修改后的算法對已經有序的情況做了優化
數組對半切分並排序后,
如果 a[mid] < a[mid + 1](左半部分的最后一個元素小於右半部分的第一個元素)
那么我們可以直接合並數組,不需要再做多余的操作
現在的輸入是一個已經排序的數組
算法唯一的比較發生在判斷 a[mid] < a[mid + 1] 這個條件時
假定數組有 N 個元素
比較次數滿足 T(N) = 2 * T(N / 2) + 1, T(1) = 0
轉化為非遞歸形式即為:T(N) = cN / 2 + N - 1
其中 c 為任意正整數
2.2.9
解答
官方給出的歸並排序實現中在 Sort 方法里初始化了 aux 數組。
源碼見:https://algs4.cs.princeton.edu/22mergesort/Merge.java.html
C#實現和官方的實現非常類似,
首先定義只接受一個參數的公開 Sort 方法,在這個方法里面初始化 aux 數組。
/// <summary> /// 利用歸並排序將數組按升序排序。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">待排序的數組。</param> public override void Sort<T>(T[] a) { T[] aux = new T[a.Length]; Sort(a, aux, 0, a.Length - 1); }
然后建立一個私有的遞歸 Sort 方法做實際的排序操作。
/// <summary> /// 自頂向下地對數組指定范圍內進行歸並排序,需要輔助數組。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">排序范圍起點。</param> /// <param name="hi">排序范圍終點。</param> private void Sort<T>(T[] a, T[] aux, int lo, int hi) where T : IComparable<T> { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; Sort(a, aux, lo, mid); Sort(a, aux, mid + 1, hi); Merge(a, aux, lo, mid, hi); }
代碼
using System; namespace Merge { /// <summary> /// 歸並排序類。 /// </summary> public class MergeSort : BaseSort { /// <summary> /// 默認構造函數。 /// </summary> public MergeSort() { } /// <summary> /// 利用歸並排序將數組按升序排序。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">待排序的數組。</param> public override void Sort<T>(T[] a) { T[] aux = new T[a.Length]; Sort(a, aux, 0, a.Length - 1); } /// <summary> /// 自頂向下地對數組指定范圍內進行歸並排序,需要輔助數組。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">排序范圍起點。</param> /// <param name="hi">排序范圍終點。</param> private void Sort<T>(T[] a, T[] aux, int lo, int hi) where T : IComparable<T> { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; Sort(a, aux, lo, mid); Sort(a, aux, mid + 1, hi); Merge(a, aux, lo, mid, hi); } /// <summary> /// 將指定范圍內的元素歸並。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">范圍起點。</param> /// <param name="mid">范圍中點。</param> /// <param name="hi">范圍終點。</param> private void Merge<T>(T[] a, T[] aux, int lo, int mid, int hi) where T : IComparable<T> { for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j]; j++; } else if (j > hi) { a[k] = aux[i]; i++; } else if (Less(aux[j], aux[i])) { a[k] = aux[j]; j++; } else { a[k] = aux[i]; i++; } } } } }
2.2.10
解答
官方同樣給出了 java 實現,如下:
private static void merge(Comparable[] a, int lo, int mid, int hi) { for (int i = lo; i <= mid; i++) aux[i] = a[i]; for (int j = mid+1; j <= hi; j++) aux[j] = a[hi-j+mid+1]; int i = lo, j = hi; for (int k = lo; k <= hi; k++) if (less(aux[j], aux[i])) a[k] = aux[j--]; else a[k] = aux[i++]; }
C# 實現見代碼部分。
代碼
using System; using Merge; namespace _2._2._10 { /// <summary> /// 歸並排序類。 /// </summary> public class MergeSort : BaseSort { /// <summary> /// 默認構造函數。 /// </summary> public MergeSort() { } /// <summary> /// 利用歸並排序將數組按升序排序。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">待排序的數組。</param> public override void Sort<T>(T[] a) { T[] aux = new T[a.Length]; Sort(a, aux, 0, a.Length - 1); } /// <summary> /// 自頂向下地對數組指定范圍內進行歸並排序,需要輔助數組。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">排序范圍起點。</param> /// <param name="hi">排序范圍終點。</param> private void Sort<T>(T[] a, T[] aux, int lo, int hi) where T : IComparable<T> { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; Sort(a, aux, lo, mid); Sort(a, aux, mid + 1, hi); Merge(a, aux, lo, mid, hi); } /// <summary> /// 將指定范圍內的元素歸並。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">范圍起點。</param> /// <param name="mid">范圍中點。</param> /// <param name="hi">范圍終點。</param> private void Merge<T>(T[] a, T[] aux, int lo, int mid, int hi) where T : IComparable<T> { // 前半部分升序復制 for (int k = lo; k <= mid; k++) { aux[k] = a[k]; } // 后半部分降序復制 for (int k = mid + 1; k <= hi; k++) { aux[k] = a[hi - k + mid + 1]; } // i 指向最左端,j 指向最右端 int i = lo, j = hi; for (int k = lo; k <= hi; k++) { if (Less(aux[j], aux[i])) { a[k] = aux[j]; j--; } else { a[k] = aux[i]; i++; } } } } }
2.2.11
解答
官方實現見:https://algs4.cs.princeton.edu/22mergesort/MergeX.java.html
在 MergeSortX 類里添加一個 CUTOFF 字段,排序時如果數組長度小於它則直接調用插入排序進行排序。
在調用歸並方法前判斷第一個有序數組的最后一個元素是否大於第二個有序數組的第一個元素,
如果大於的話就不需要調用歸並了,直接首尾相接即可。
每次歸並都需要兩個數組,一個用於存放歸並結果,這個數組中的內容是無關緊要的;
另一個則保存了歸並前的數組,用於實際的歸並過程。
歸並結束后,前一個數組變成歸並后的有序結果(也就是下一次歸並時的「歸並前數組」),后一個數組中的內容則不再有用。
我們可以看到這兩個數組的角色在下一次歸並時正好可以互換。
要注意的是,歸並次數總是一個奇數(左側歸並+右側歸並+總歸並),因此在第一次調用 Sort 方法時應該把 aux 和 a 互換傳入。
代碼
using System; namespace Merge { /// <summary> /// 優化后的歸並排序類。 /// </summary> public class MergeSortX : BaseSort { /// <summary> /// 對小於 CUTOFF 的數組使用插入排序。 /// </summary> private static int CUTOFF = 7; /// <summary> /// 默認構造函數。 /// </summary> public MergeSortX() { } /// <summary> /// 設置啟用插入排序的閾值,小於該閾值的數組將采用插入排序。 /// </summary> /// <param name="cutoff">新的閾值。</param> public void SetCutOff(int cutoff) => CUTOFF = cutoff; /// <summary> /// 將指定范圍內的元素歸並。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="src">原始數組。</param> /// <param name="dst">目標數組。</param> /// <param name="lo">范圍起點。</param> /// <param name="mid">范圍中點。</param> /// <param name="hi">范圍終點。</param> private void Merge<T>(T[] src, T[] dst, int lo, int mid, int hi) where T : IComparable<T> { int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) dst[k] = src[j++]; else if (j > hi) dst[k] = src[i++]; else if (Less(src[j], src[i])) dst[k] = src[j++]; else dst[k] = src[i++]; } } /// <summary> /// 自頂向下地對數組指定范圍內進行歸並排序,需要輔助數組。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="src">原數組。</param> /// <param name="dst">輔助數組。</param> /// <param name="lo">排序范圍起點。</param> /// <param name="hi">排序范圍終點。</param> private void Sort<T>(T[] src, T[] dst, int lo, int hi) where T : IComparable<T> { // 小於 CUTOFF 的數組調用插入排序 if (hi <= lo + CUTOFF) { InsertionSort insertion = new InsertionSort(); insertion.Sort(dst, lo, hi); return; } int mid = lo + (hi - lo) / 2; // 交換輔助數組和原數組 Sort(dst, src, lo, mid); Sort(dst, src, mid + 1, hi); // 已排序的數組直接合並 if (!Less(src[mid + 1], src[mid])) { Array.Copy(src, lo, dst, lo, hi - lo + 1); return; } Merge(src, dst, lo, mid, hi); } /// <summary> /// 利用優化后的歸並排序對數組 a 排序。 /// </summary> /// <typeparam name="T">數組中的元素類型。</typeparam> /// <param name="a">需要排序的數組。</param> public override void Sort<T>(T[] a) { T[] aux = new T[a.Length]; a.CopyTo(aux, 0); // aux 和 a 需要交換 Sort(aux, a, 0, a.Length - 1); } } }
2.2.12
解答
中文版的翻譯比較難理解
實際上就是另一種歸並排序的實現方式
先把數組分成若干個大小為 M 的塊
對於每個塊,用選擇排序進行排序
隨后遍歷數組,將各個塊歸並起來
代碼
using System; using Merge; namespace _2._2._12 { /// <summary> /// 歸並排序類。 /// </summary> public class MergeSort : BaseSort { /// <summary> /// 默認構造函數。 /// </summary> public MergeSort() { } /// <summary> /// 利用歸並排序將數組按升序排序。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">待排序的數組。</param> public override void Sort<T>(T[] a) { Sort(a, 1); } /// <summary> /// 利用分塊法進行歸並排序。 /// </summary> /// <typeparam name="T">待排序的數組內容。</typeparam> /// <param name="a">待排序的數組。</param> /// <param name="M">分塊大小。</param> public void Sort<T>(T[] a, int M) where T : IComparable<T> { int blockNum = a.Length / M; SelectionSort selection = new SelectionSort(); // 對塊進行選擇排序。 for (int i = 0; i < blockNum; i++) { int lo = i * M; int hi = (i + 1) * M - 1; selection.Sort(a, lo, hi); } // 將各個塊合並。 for (int i = 0; i < blockNum - 1; i++) { Merge(a, 0, (i + 1) * M - 1, (i + 2) * M - 1); } } /// <summary> /// 將指定范圍內的元素歸並。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="lo">范圍起點。</param> /// <param name="mid">范圍中點。</param> /// <param name="hi">范圍終點。</param> private void Merge<T>(T[] a, int lo, int mid, int hi) where T : IComparable<T> { T[] aux = new T[hi - lo + 1]; for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j]; j++; } else if (j > hi) { a[k] = aux[i]; i++; } else if (Less(aux[j], aux[i])) { a[k] = aux[j]; j++; } else { a[k] = aux[i]; i++; } } } } }
2.2.13
解答
假設對三個數進行排序,這三個數是:35,10,17
三個數排序的決策樹如下,結點代表比較對應位置上的數。
對於 35,10,17 來說,路徑遵循右、左、左,最后得到的結果就是 2 3 1(10,17,35)。
我們可以發現決策樹上的每一個葉子節點都代表一種排列順序,對於 N 個數,葉子節點就有 N! 個
根據二叉樹的性質,高度為 h 的二叉樹最多有 2^h 個葉子節點
那么,對於 N 個數,決策樹的高度 h 的最小值可以通過下面這個式子得出來
2^h >= n!
h >= log(n!)
因此可以得到決策樹高度 h 的最小值是 log(n!)
接下來我們來計算平均路徑長度
我們令函數 H(k) 代表有 k 個葉子節點的平衡決策樹的所有路徑長度之和
上例中 H(6) = 2 + 2 + 3 + 3 + 3 + 3 = 16
由於平衡決策樹的性質,H(k) = 2H(k / 2) + k
(加上 k 的原因:左右子樹的高度比整個樹的高度小 1,因此每條路徑的長度都必須加 1,總共多加了 k 次)
因此 H(k) = klogk
現在令 k = n!
H(n!) = n!log(n!)
由於每次排序時我們只經過某一條路徑(上例中我們只經過了右、左、左這條路徑)
而且每種路徑的出現概率相等
因此平均比較次數應該為 H(n!) / n! = log(n!) = nlog(n)
證明完畢
2.2.14
解答
比較兩個有序隊列的第一個元素,取較小的一個出隊並放入額外建立的隊列中。
重復上述步驟直到兩個隊列都為空。
代碼
/// <summary> /// 歸並兩個有序隊列。輸入隊列將被清空。 /// </summary> /// <typeparam name="T">有序隊列的元素類型。</typeparam> /// <param name="a">需要歸並的隊列。</param> /// <param name="b">需要歸並的隊列。</param> /// <returns>歸並后的新隊列。</returns> static Queue<T> Merge<T>(Queue<T> a, Queue<T> b) where T : IComparable<T> { Queue<T> sortedQueue = new Queue<T>(); while (!a.IsEmpty() && !b.IsEmpty()) { if (a.Peek().CompareTo(b.Peek()) < 0) sortedQueue.Enqueue(a.Dequeue()); else sortedQueue.Enqueue(b.Dequeue()); } while (!a.IsEmpty()) sortedQueue.Enqueue(a.Dequeue()); while (!b.IsEmpty()) sortedQueue.Enqueue(b.Dequeue()); return sortedQueue; }
2.2.15
解答
程序思路題目已經給出,按照題意實現即可。
Merge 方法可以直接使用前一題的實現。
代碼
using System; namespace _2._2._15 { /// <summary> /// 利用隊列歸並實現的自底向上的歸並排序。 /// </summary> class MergeSortQueue { /// <summary> /// 默認構造函數。 /// </summary> public MergeSortQueue() { } /// <summary> /// 利用隊列歸並進行自底向上的歸並排序。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="a">需要排序的數組。</param> public void Sort<T>(T[] a) where T : IComparable<T> { Queue<Queue<T>> queueList = new Queue<Queue<T>>(); for (int i = 0; i < a.Length; i++) { Queue<T> temp = new Queue<T>(); temp.Enqueue(a[i]); queueList.Enqueue(temp); } while (queueList.Size() != 1) { int times = queueList.Size() / 2; for (int i = 0; i < times; i++) { Queue<T> A = queueList.Dequeue(); Queue<T> B = queueList.Dequeue(); queueList.Enqueue(Merge(A, B)); } } Queue<T> result = queueList.Dequeue(); for (int i = 0; i < a.Length; i++) { a[i] = result.Dequeue(); } } /// <summary> /// 歸並兩個有序隊列。輸入隊列將被清空。 /// </summary> /// <typeparam name="T">有序隊列的元素類型。</typeparam> /// <param name="a">需要歸並的隊列。</param> /// <param name="b">需要歸並的隊列。</param> /// <returns>歸並后的新隊列。</returns> public static Queue<T> Merge<T>(Queue<T> a, Queue<T> b) where T : IComparable<T> { Queue<T> sortedQueue = new Queue<T>(); while (!a.IsEmpty() && !b.IsEmpty()) { if (a.Peek().CompareTo(b.Peek()) < 0) sortedQueue.Enqueue(a.Dequeue()); else sortedQueue.Enqueue(b.Dequeue()); } while (!a.IsEmpty()) sortedQueue.Enqueue(a.Dequeue()); while (!b.IsEmpty()) sortedQueue.Enqueue(b.Dequeue()); return sortedQueue; } } }
2.2.16
解答
自然歸並排序的一個示例如下圖所示:

基本過程和自底向上的歸並排序類似,只是每次歸並的塊大小不一定相同。
時間分析
隨着有序塊的變大,排序耗時會縮短,但增長的數量級會變高(歸並的平均塊大小變大了)。
代碼
using System; using System.Diagnostics; namespace Merge { /// <summary> /// 自然的歸並排序。 /// </summary> public class MergeSortNatural : BaseSort { /// <summary> /// 默認構造函數。 /// </summary> public MergeSortNatural() { } /// <summary> /// 利用自然的歸並排序進行自底向上的排序。 /// </summary> /// <typeparam name="T">用於排序的元素類型。</typeparam> /// <param name="a">需要排序的數組。</param> public override void Sort<T>(T[] a) { T[] aux = new T[a.Length]; while (true) { // 找到第一個塊 int lo = 0; int mid = FindBlock(lo, a) - 1; if (mid == a.Length - 1) break; while (mid < a.Length - 1) { int hi = FindBlock(mid + 1, a) + mid; Merge(lo, mid, hi, a, aux); lo = hi + 1; mid = FindBlock(lo, a) + lo - 1; } } Debug.Assert(IsSorted(a)); } /// <summary> /// 將兩個塊歸並。 /// </summary> /// <typeparam name="T">數組的元素類型。</typeparam> /// <param name="lo">第一個塊的開始下標。</param> /// <param name="mid">第一個塊的結束下標(第二個塊的開始下標 - 1)。</param> /// <param name="hi">第二個塊的結束下標。</param> /// <param name="a">需要歸並的數組。</param> /// <param name="aux">輔助數組。</param> private void Merge<T>(int lo, int mid, int hi, T[] a, T[] aux) where T : IComparable<T> { for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j]; j++; } else if (j > hi) { a[k] = aux[i]; i++; } else if (Less(aux[j], aux[i])) { a[k] = aux[j]; j++; } else { a[k] = aux[i]; i++; } } } /// <summary> /// 獲取下一個有序塊。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="lo">查找起點。</param> /// <param name="a">用於查找的數組。</param> /// <returns>塊的大小。</returns> private int FindBlock<T>(int lo, T[] a) where T : IComparable<T> { int size = 1; for (int i = lo; i < a.Length - 1; i++) { if (Less(a[i], a[i + 1]) || a[i].Equals(a[i + 1])) size++; else break; } return size; } } }
2.2.17
解答
排序方式和 2.2.16 十分類似,不再贅述,這里介紹一下歸並方法。
如 gif 圖所示,先把要歸並的兩個鏈表拆出來,隨后確定表頭位置,然后進行歸並即可。
歸並結束后返回 first。
結果分析如下圖所示:
隨着有序部分的增加,對於相同大小的數組自然歸並排序的耗時會縮短。
對於有序部分相同的情況,隨着數組大小的倍增,耗時呈現了O(nlogn)的趨勢。
代碼
using System; using System.Diagnostics; using Merge; namespace _2._2._17 { /// <summary> /// 自然的歸並排序。 /// </summary> public class MergeSortNatural : BaseSort { /// <summary> /// 默認構造函數。 /// </summary> public MergeSortNatural() { } /// <summary> /// 利用自然的歸並排序進行自底向上的排序。 /// </summary> /// <typeparam name="T">用於排序的元素類型。</typeparam> /// <param name="a">需要排序的數組。</param> public override void Sort<T>(T[] a) { T[] aux = new T[a.Length]; while (true) { // 找到第一個塊 int lo = 0; int mid = FindBlock(lo, a) - 1; if (mid == a.Length - 1) break; while (mid < a.Length - 1) { int hi = FindBlock(mid + 1, a) + mid; Merge(lo, mid, hi, a, aux); lo = hi + 1; mid = FindBlock(lo, a) + lo; } } Debug.Assert(IsSorted(a)); } /// <summary> /// 利用自然的歸並排序將鏈表排序。 /// </summary> /// <typeparam name="T">鏈表元素類型。</typeparam> /// <param name="a">等待排序的鏈表。</param> public void Sort<T>(LinkedList<T> a) where T : IComparable<T> { while (true) { // 找到第一個塊 Node<T> lo = a.GetFirst(); Node<T> mid = FindBlock(lo); if (mid.next == null) break; while (mid.next != null) { Node<T> hi = FindBlock(mid.next); if (lo == a.GetFirst()) a.SetFirst(Merge(lo, mid, hi)); else lo.next = Merge(lo.next, mid, hi); // 跳到表尾 if (Less(hi.item, mid.item)) lo = mid; else lo = hi; if (lo.next != null) mid = FindBlock(lo.next); } } } /// <summary> /// 將兩個塊歸並。 /// </summary> /// <typeparam name="T">數組的元素類型。</typeparam> /// <param name="lo">第一個塊的開始下標。</param> /// <param name="mid">第一個塊的結束下標(第二個塊的開始下標 - 1)。</param> /// <param name="hi">第二個塊的結束下標。</param> /// <param name="a">需要歸並的數組。</param> /// <param name="aux">輔助數組。</param> private void Merge<T>(int lo, int mid, int hi, T[] a, T[] aux) where T : IComparable<T> { for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j]; j++; } else if (j > hi) { a[k] = aux[i]; i++; } else if (Less(aux[j], aux[i])) { a[k] = aux[j]; j++; } else { a[k] = aux[i]; i++; } } } /// <summary> /// 將兩個有序鏈表塊歸並,返回新的表頭。 /// </summary> /// <typeparam name="T">鏈表元素類型。</typeparam> /// <param name="lo">第一個有序塊起點。</param> /// <param name="mid">第一個有序塊終點(第二個有序塊起點的前驅)。</param> /// <param name="hi">第二個有序塊的終點。</param> /// <returns>新的表頭。</returns> private Node<T> Merge<T>(Node<T> lo, Node<T> mid, Node<T> hi) where T : IComparable<T> { Node<T> after = hi.next; // 要合並的兩個塊之后的元素 Node<T> first = null; Node<T> i = lo; // 鏈表1 Node<T> j = mid.next; // 鏈表2 // 切割鏈表 mid.next = null; hi.next = null; Node<T> current = null; // 決定新的表頭 if (Less(i.item, j.item)) { current = i; i = i.next; } else { current = j; j = j.next; } first = current; // 歸並表 while (i != null && j != null) { if (Less(i.item, j.item)) { current.next = i; i = i.next; current = current.next; } else { current.next = j; j = j.next; current = current.next; } } if (i == null) current.next = j; else current.next = i; // 連接表尾(鏈表 1 的尾部或者鏈表 2 的尾部) if (mid.next == null) mid.next = after; else hi.next = after; return first; } /// <summary> /// 獲取下一個有序塊。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="lo">查找起點。</param> /// <param name="a">用於查找的數組。</param> /// <returns>塊的大小。</returns> private int FindBlock<T>(int lo, T[] a) where T : IComparable<T> { int size = 1; for (int i = lo; i < a.Length - 1; i++) { if (Less(a[i], a[i + 1]) || a[i].Equals(a[i + 1])) size++; else break; } return size; } /// <summary> /// 獲取鏈表的下一個有序塊。 /// </summary> /// <typeparam name="T">鏈表的元素類型。</typeparam> /// <param name="lo">查找的起始結點。</param> /// <returns>有序塊的最后一個元素結點。</returns> private Node<T> FindBlock<T>(Node<T> lo) where T : IComparable<T> { Node<T> hi = lo; while (hi.next != null) { if (Less(hi.item, hi.next.item) || hi.item.Equals(hi.next.item)) hi = hi.next; else break; } return hi; } } }
2.2.18
解答
可以在用歸並排序的方法做。
將歸並時取兩邊較小的元素改為隨機取一側的值,即可實現打亂的效果。
算法的分析和普通歸並排序一致,滿足題目要求。
代碼
分治法打亂鏈表的實現。
using System; namespace _2._2._18 { /// <summary> /// 分治法打亂鏈表。
/// </summary> public class MergeShuffle { /// <summary> /// 默認構造函數。 /// </summary> public MergeShuffle() { } /// <summary> /// 利用分治法打亂鏈表。 /// </summary> /// <typeparam name="T">鏈表元素類型。</typeparam> /// <param name="a">等待打亂的鏈表。</param> public void Shuffle<T>(LinkedList<T> a) { int blockLen = 1; Random random = new Random(); while (blockLen <= a.Size()) { // 找到第一個塊 Node<T> lo = a.GetFirst(); Node<T> mid = FindBlock(lo, blockLen); if (mid.next == null) break; while (mid.next != null) { Node<T> hi = FindBlock(mid.next, blockLen); Node<T>[] result; if (lo == a.GetFirst()) { result = Merge(lo, mid, hi, random); a.SetFirst(result[0]); } else { result = Merge(lo.next, mid, hi, random); lo.next = result[0]; } // 跳到表尾 lo = result[1]; if (lo.next != null) mid = FindBlock(lo.next, blockLen); else mid = lo; } blockLen *= 2; } } /// <summary> /// 將兩個有序鏈表塊隨機歸並,返回新的表頭。 /// </summary> /// <typeparam name="T">鏈表元素類型。</typeparam> /// <param name="lo">第一個塊起點。</param> /// <param name="mid">第一個塊終點(第二個塊起點的前驅)。</param> /// <param name="hi">第二個塊的終點。</param> /// <returns>新的表頭。</returns> private Node<T>[] Merge<T>(Node<T> lo, Node<T> mid, Node<T> hi, Random random) { Node<T> after = hi.next; // 要合並的兩個塊之后的元素 Node<T> first = null; Node<T>[] result = new Node<T>[2]; Node<T> i = lo; // 鏈表1 Node<T> j = mid.next; // 鏈表2 // 切割鏈表 mid.next = null; hi.next = null; Node<T> current = null; // 決定新的表頭 if (random.NextDouble() >= 0.5) { current = i; i = i.next; } else { current = j; j = j.next; } first = current; // 歸並表 while (i != null && j != null) { if (random.NextDouble() >= 0.5) { current.next = i; i = i.next; current = current.next; } else { current.next = j; j = j.next; current = current.next; } } if (i == null) current.next = j; else current.next = i; // 連接表尾(鏈表 1 的尾部或者鏈表 2 的尾部) if (mid.next == null) { mid.next = after; result[1] = mid; } else { hi.next = after; result[1] = hi; } result[0] = first; return result; } /// <summary> /// 獲取從指定位置開始定長的鏈表。 /// </summary> /// <typeparam name="T">鏈表的元素類型。</typeparam> /// <param name="lo">鏈表的起始結點。</param> /// <param name="length">需要獲取的鏈表長度。</param> /// <returns>結果鏈表的最后一個元素結點。</returns> private Node<T> FindBlock<T>(Node<T> lo, int length) { Node<T> hi = lo; for (int i = 0; i < length - 1 && hi.next != null; i++) { hi = hi.next; } return hi; } } }
2.2.19
解答
官方實現:https://algs4.cs.princeton.edu/22mergesort/Inversions.java.html
事實上只要在歸並排序的時候統計 Less(aux[j], aux[i]) 滿足的次數即可,這個次數就是我們要的值。
代碼
using System; using Merge; namespace _2._2._19 { /// <summary> /// 歸並排序類。 /// </summary> public class MergeSort : BaseSort { public int Counter = 0; /// <summary> /// 默認構造函數。 /// </summary> public MergeSort() { } /// <summary> /// 利用歸並排序將數組按升序排序。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">待排序的數組。</param> public override void Sort<T>(T[] a) { T[] aux = new T[a.Length]; Sort(a, aux, 0, a.Length - 1); } /// <summary> /// 自頂向下地對數組指定范圍內進行歸並排序,需要輔助數組。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">排序范圍起點。</param> /// <param name="hi">排序范圍終點。</param> private void Sort<T>(T[] a, T[] aux, int lo, int hi) where T : IComparable<T> { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; Sort(a, aux, lo, mid); Sort(a, aux, mid + 1, hi); Merge(a, aux, lo, mid, hi); } /// <summary> /// 將指定范圍內的元素歸並。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">范圍起點。</param> /// <param name="mid">范圍中點。</param> /// <param name="hi">范圍終點。</param> private void Merge<T>(T[] a, T[] aux, int lo, int mid, int hi) where T : IComparable<T> { for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j]; j++; } else if (j > hi) { a[k] = aux[i]; i++; } else if (Less(aux[j], aux[i])) { a[k] = aux[j]; this.Counter += mid - i + 1; // 統計逆序對數 j++; } else { a[k] = aux[i]; i++; } } } } }
2.2.20
解答
官方實現:https://algs4.cs.princeton.edu/22mergesort/Merge.java.html
把 Sort 方法中傳入的 a 數組換成一個 index 數組,將 Merge 方法中的判斷改為 Less(a[aux[j]], a[aux[i]]) 即可。
代碼
using System; using Merge; namespace _2._2._20 { /// <summary> /// 歸並排序類。 /// </summary> public class MergeSort : BaseSort { /// <summary> /// 默認構造函數。 /// </summary> public MergeSort() { } /// <summary> /// 利用歸並排序將數組按升序排序。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">待排序的數組。</param> public int[] IndexSort<T>(T[] a) where T : IComparable<T> { int[] aux = new int[a.Length]; int[] index = new int[a.Length]; for (int i = 0; i < a.Length; i++) { index[i] = i; } Sort(a, index, aux, 0, a.Length - 1); return index; } /// <summary> /// 自頂向下地對數組指定范圍內進行歸並排序,需要輔助數組。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">排序范圍起點。</param> /// <param name="hi">排序范圍終點。</param> private void Sort<T>(T[] a, int[] index, int[] aux, int lo, int hi) where T : IComparable<T> { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; Sort(a, index, aux, lo, mid); Sort(a, index, aux, mid + 1, hi); Merge(a, index, aux, lo, mid, hi); } /// <summary> /// 將指定范圍內的元素歸並。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">范圍起點。</param> /// <param name="mid">范圍中點。</param> /// <param name="hi">范圍終點。</param> private void Merge<T>(T[] a, int[] index, int[] aux, int lo, int mid, int hi) where T : IComparable<T> { for (int k = lo; k <= hi; k++) { aux[k] = index[k]; } int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) { index[k] = aux[j]; j++; } else if (j > hi) { index[k] = aux[i]; i++; } else if (Less(a[aux[j]], a[aux[i]])) { index[k] = aux[j]; j++; } else { index[k] = aux[i]; i++; } } } public override void Sort<T>(T[] a) { throw new NotImplementedException(); } } }
2.2.21
解答
對三份列表進行歸並排序(O(nlogn)),隨后遍歷一遍其中的一份表,
用二分查找檢查在其余兩個表中是否存在相同的姓名(O(nlogn))
代碼
using System; using Merge; namespace _2._2._21 { /* * 2.2.21 * * 一式三份。 * 給定三個列表, * 每個列表中包含 N 個名字, * 編寫一個線性對數級別的算法來判定三份列表中是否含有公共的名字, * 如果有,返回第一個被找到的這種名字。 * */ class Program { static void Main(string[] args) { string[] name1 = new string[] { "Noah", "Liam", "Jacob", "Mason" }; string[] name2 = new string[] { "Sophia", "Emma", "Mason", "Ava" }; string[] name3 = new string[] { "Mason", "Marcus", "Alexander", "Ava" }; MergeSort mergeSort = new MergeSort(); mergeSort.Sort(name1); mergeSort.Sort(name2); mergeSort.Sort(name3); for (int i = 0; i < name1.Length; i++) { if (BinarySearch(name1[i], name2, 0, name1.Length) != -1 && BinarySearch(name1[i], name3, 0, name1.Length) != -1) { Console.WriteLine(name1[i]); break; } } } /// <summary> /// 二分查找,返回目標元素的下標,沒有結果則返回 -1。 /// </summary> /// <typeparam name="T">查找的元素類型。</typeparam> /// <param name="key">要查找的目標值。</param> /// <param name="array">用於查找的目標范圍。</param> /// <param name="lo">查找的起始下標。</param> /// <param name="hi">查找的終止下標。</param> /// <returns>找到則返回元素下標,否則返回 -1。</returns> static int BinarySearch<T>(T key, T[] array, int lo, int hi) where T : IComparable<T> { while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (array[mid].Equals(key)) return mid; else if (array[mid].CompareTo(key) < 0) lo = mid + 1; else hi = mid - 1; } return -1; } } }
2.2.22
解答
代碼
using System; using System.Diagnostics; namespace Merge { /// <summary> /// 三向歸並排序。 /// </summary> public class MergeSortThreeWay : BaseSort { /// <summary> /// 默認構造函數。 /// </summary> public MergeSortThreeWay() { } /// <summary> /// 利用三項歸並排序將數組按升序排序。 /// </summary> /// <typeparam name="T">數組中的元素類型。</typeparam> /// <param name="a">待排序的數組。</param> public override void Sort<T>(T[] a) { T[] aux = new T[a.Length]; Sort(a, aux, 0, a.Length - 1); Debug.Assert(IsSorted(a)); } /// <summary> /// 自頂向下地對數組指定范圍內進行三向歸並排序,需要輔助數組。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">排序范圍起點。</param> /// <param name="hi">排序范圍終點。</param> private void Sort<T>(T[] a, T[] aux, int lo, int hi) where T : IComparable<T> { if (hi <= lo) // 小於或等於一個元素 return; int lmid = lo + (hi - lo) / 3; int rmid = hi - (hi - lo) / 3; Sort(a, aux, lo, lmid); Sort(a, aux, lmid + 1, rmid); Sort(a, aux, rmid + 1, hi); Merge(a, aux, lo, lmid, rmid, hi); } /// <summary> /// 返回兩個元素中較小的那個。 /// </summary> /// <typeparam name="T">比較的元素類型。</typeparam> /// <param name="a">用於比較的元素。</param> /// <param name="b">用於比較的元素。</param> /// <returns>較小的元素。</returns> private T Min<T>(T a, T b) where T : IComparable<T> { if (Less(a, b)) return a; return b; } /// <summary> /// 將指定范圍內的元素歸並。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">范圍起點。</param> /// <param name="lmid">范圍三分之一點。</param> /// <param name="rmid">范圍三分之二點。</param> /// <param name="hi">范圍終點。</param> private void Merge<T>(T[] a, T[] aux, int lo, int lmid, int rmid, int hi) where T : IComparable<T> { for (int l = lo; l <= hi; l++) { aux[l] = a[l]; } int i = lo, j = lmid + 1, k = rmid + 1; for (int l = lo; l <= hi; l++) { int flag = 0; if (i > lmid) flag += 1; if (j > rmid) flag += 10; if (k > hi) flag += 100; switch (flag) { case 0: // 三個數組都還沒有取完 T min = Min(aux[i], Min(aux[j], aux[k])); if (min.Equals(aux[i])) a[l] = aux[i++]; else if (min.Equals(aux[j])) a[l] = aux[j++]; else a[l] = aux[k++]; break; case 1: // 只有第一個數組取完了 if (Less(aux[j], aux[k])) a[l] = aux[j++]; else a[l] = aux[k++]; break; case 10: // 只有第二個數組取完了 if (Less(aux[i], aux[k])) a[l] = aux[i++]; else a[l] = aux[k++]; break; case 100: // 只有第三個數組取完了 if (Less(aux[i], aux[j])) a[l] = aux[i++]; else a[l] = aux[j++]; break; case 11: // 第一、二個數組取完了 a[l] = aux[k++]; break; case 101: // 第一、三個數組取完了 a[l] = aux[j++]; break; case 110: // 第二、三個數組取完了 a[l] = aux[i++]; break; } } } } }
2.2.23
解答

閾值合適時,大約會有10%的性能提升。
閾值在 10 以下都是比較合適的。
代碼
using System; using Merge; namespace _2._2._23 { /* * 2.2.23 * * 改進。 * 用實驗評估正文中所提到的歸並排序的三項改進(請見練習 2.2.11)的效果, * 並比較正文中實現的歸並排序和練習 2.2.10 所實現的歸並排序之間的性能。 * 根據經驗給出應該在何時為子數組切換到插入排序。 * */ class Program { static void Main(string[] args) { MergeSort mergeSort = new MergeSort(); MergeSortX mergeSortX = new MergeSortX(); MergeSortUnstable mergeSortUnstable = new MergeSortUnstable(); int n = 1000000; int cutoff = 2; int trialTime = 4; Console.WriteLine("歸並排序改進前與改進后的比較:"); Console.WriteLine("數組\t耗時1\t耗時2\t閾值\t比率"); for (int i = 0; i < 20; i++) { double mergeSortTime = 0; double mergeSortXTime = 0; mergeSortX.SetCutOff(cutoff); for (int j = 0; j < trialTime; j++) { int[] a = SortCompare.GetRandomArrayInt(n); int[] b = new int[a.Length]; a.CopyTo(b, 0); mergeSortTime += SortCompare.Time(mergeSort, a); mergeSortXTime += SortCompare.Time(mergeSortX, b); } mergeSortTime /= trialTime; mergeSortXTime /= trialTime; Console.WriteLine(n + "\t" + mergeSortTime + "\t" + mergeSortXTime + "\t" + cutoff + "\t" + mergeSortTime / mergeSortXTime); cutoff++; } n = 100000; Console.WriteLine("穩定歸並排序與不穩定版本的比較:"); Console.WriteLine("數組\t耗時1\t耗時2\t比率"); for (int i = 0; i < 6; i++) { double mergeSortTime = 0; double mergeSortUnstableTime = 0; for (int j = 0; j < trialTime; j++) { int[] a = SortCompare.GetRandomArrayInt(n); int[] b = new int[a.Length]; a.CopyTo(b, 0); mergeSortTime += SortCompare.Time(mergeSort, a); mergeSortUnstableTime += SortCompare.Time(mergeSortUnstable, b); } mergeSortTime /= trialTime; mergeSortUnstableTime /= trialTime; Console.WriteLine(n + "\t" + mergeSortTime + "\t" + mergeSortUnstableTime + "\t" + mergeSortTime / mergeSortUnstableTime); n *= 2; } } } }
2.2.24
解答
代碼
using System; using Merge; namespace _2._2._24 { /* * 2.2.24 * * 改進的有序測試。 * 在實驗中用大型隨機數組評估練習 2.2.8 所做的修改的效果。 * 根據經驗用 N(被排序的原始數組的大小)的函數描述條件語句 * (a[mid] <= a[mid + 1])成立(無論數組是否有序)的次數。 * */ class Program { static void Main(string[] args) { MergeSortX mergeSortX = new MergeSortX(); int n = 10000; int trialTimes = 10; Console.WriteLine("數組\t平均命中次數"); for (int i = 0; i < 4; i++) { int avgHit = 0; for (int j = 0; j < trialTimes; j++) { mergeSortX.ResetHitTime(); int[] a = SortCompare.GetRandomArrayInt(n); mergeSortX.Sort(a); avgHit += mergeSortX.GetHitTime(); } Console.WriteLine(n + "\t" + avgHit / trialTimes); n *= 10; } } } }
2.2.25
解答
事實上 k 的取值無關緊要,實驗也證明了這一點。
算法大致可以分為以下幾個步驟
首先將數組划為 k 份,用一個數組 mids 記錄這 k 個子數組的分割位置
隨后遞歸的調用 Sort 方法,將這 k 個子數組排序
隨后將這 k 個子數組歸並,
每次歸並時遍歷取 k 個子數組中值最小的一個,然后對應子數組的指示器 + 1
上面這一步是 O(k) 的,可以用堆或者敗者樹優化為對數級別
代碼
using System; using System.Diagnostics; namespace Merge { /// <summary> /// k 路歸並排序。 /// </summary> public class MergeSortKWay : BaseSort { /// <summary> /// 同時歸並的數組數目。 /// </summary> public int K { get; set; } /// <summary> /// 默認構造函數。 /// </summary> public MergeSortKWay() { this.K = 2; } /// <summary> /// 用 k 向歸並排序對數組 a 進行排序。 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="a"></param> /// <exception cref="ArgumentOutOfRangeException">數組長度小於 K 值時拋出異常。</exception> public override void Sort<T>(T[] a) { if (this.K > a.Length) throw new ArgumentOutOfRangeException("數組長度不能小於 K 值!"); T[] aux = new T[a.Length]; Sort(a, aux, 0, a.Length - 1); Debug.Assert(IsSorted(a)); } /// <summary> /// 自頂向下地對數組指定范圍內進行 k 向歸並排序,需要輔助數組。 /// </summary> /// <typeparam name="T">需要排序的元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">排序范圍起點。</param> /// <param name="hi">排序范圍終點。</param> private void Sort<T>(T[] a, T[] aux, int lo, int hi) where T : IComparable<T> { if (hi <= lo) // 小於或等於一個元素 return; int[] mids = new int[this.K - 1]; int steps = (hi - lo) / this.K; mids[0] = lo + steps; for (int i = 1; i < this.K - 1; i++) { mids[i] = mids[i - 1] + steps; if (mids[i] > hi) // 防止溢出 mids[i] = hi; } Sort(a, aux, lo, mids[0]); for (int i = 1; i < this.K - 1; i++) { Sort(a, aux, mids[i - 1] + 1, mids[i]); } Sort(a, aux, mids[this.K - 2] + 1, hi); Merge(a, aux, lo, mids, hi); } /// <summary> /// 將指定范圍內的元素歸並。 /// </summary> /// <typeparam name="T">數組元素類型。</typeparam> /// <param name="a">原數組。</param> /// <param name="aux">輔助數組。</param> /// <param name="lo">范圍起點。</param> /// <param name="mids">范圍中間點。</param> /// <param name="hi">范圍終點。</param> private void Merge<T>(T[] a, T[] aux, int lo, int[] mids, int hi) where T : IComparable<T> { for (int l = lo; l <= hi; l++) { aux[l] = a[l]; } int[] pointers = new int[this.K]; // 標記每個數組的當前歸並位置 pointers[0] = lo; // 開始時歸並位置處於每個子數組的起始 for (int i = 1; i < this.K; i++) { pointers[i] = mids[i - 1] + 1; } // 開始歸並 for (int i = lo; i <= hi; i++) { // 找到最小值 T min; int minPointerIndex = 0; bool isInit = true; if (pointers[this.K - 1] > hi) { min = default(T); // 初始化以避免編譯錯誤 } else { min = aux[pointers[this.K - 1]]; minPointerIndex = this.K - 1; isInit = false; } for (int j = 0; j < this.K - 1; j++) { if (pointers[j] > mids[j]) // 當前數組已經用完 continue; if (isInit) // 第一次賦值 { isInit = false; min = aux[pointers[j]]; minPointerIndex = j; continue; } if (Less(aux[pointers[j]], min)) { min = aux[pointers[j]]; minPointerIndex = j; } } // 將最小值賦給歸並數組,對應子數組的歸並位置+1 a[i] = min; pointers[minPointerIndex]++; } } } }
2.2.26
解答
差距還是比較明顯的,由於 Merge 會調用多次,而用於啟動遞歸的 Sort 方法只會調用一次。
代碼
using System; using Merge; namespace _2._2._26 { /* * 2.2.26 * * 創建數組。 * 使用 SortCompare 粗略比較在你的計算機上 * 在 merge() 中和在 sort() 中創建 aux[] 的性能差異。 * */ class Program { static void Main(string[] args) { AuxInSortMergeSort auxInSort = new AuxInSortMergeSort(); AuxInMergeMergeSort auxInMerge = new AuxInMergeMergeSort(); int[] data1 = SortCompare.GetRandomArrayInt(100000); int[] data2 = new int[data1.Length]; data1.CopyTo(data2, 0); Console.WriteLine("在Sort中創建aux[]\t" + SortCompare.Time(auxInSort, data1) + "ms"); Console.WriteLine("在Merge中創建aux[]\t" + SortCompare.Time(auxInMerge, data2) + "ms"); } } }
2.2.27
解答
代碼
using System; using Merge; namespace _2._2._27 { /* * 2.2.27 * * 子數組長度。 * 用歸並將大型隨機數組排序, * 根據經驗用 N (某次歸並時兩個子數組的長度之和) * 的函數估計當一個子數組用盡時另一個子數組的平均長度。 * */ class Program { static void Main(string[] args) { int arraySize = 1000000; NotifiedMergeSort sort = new NotifiedMergeSort(arraySize); for (int i = 0; i < 100; i++) { int[] data = SortCompare.GetRandomArrayInt(arraySize); sort.Sort(data); } Console.WriteLine("n\trest\ttimes"); for (int i = 0; i < sort.NArraySize.Length; i++) { if (sort.NArraySize[i] != 0) Console.WriteLine(i + "\t" + sort.NArraySize[i] / sort.NArraySizeTime[i] + "\t" + sort.NArraySizeTime[i]); } // 大致上是一個對數函數 } } }
2.2.28
解答
代碼
using System; using Merge; namespace _2._2._28 { /* * 2.2.28 * * 自頂向下和自底向上。 * 對於 N=10^3、10^4、10^5 和 10^6, * 使用 SortCompare 比較自頂向下和自底向上的歸並排序的性能。 * */ class Program { static void Main(string[] args) { int n = 1000; MergeSort topBottomMergeSort = new MergeSort(); MergeSortBU bottomUpMergeSort = new MergeSortBU(); int trialTimes = 100; for (int i = 0; i < 4; i++) { Console.Write("數組大小:" + n + "\t"); int time1 = 0, time2 = 0; for (int j = 0; j < trialTimes; j++) { double[] data1 = SortCompare.GetRandomArrayDouble(n); double[] data2 = new double[n]; data1.CopyTo(data2, 0); time1 += (int)SortCompare.Time(topBottomMergeSort, data1); time2 += (int)SortCompare.Time(bottomUpMergeSort, data2); } Console.WriteLine("自頂向下:" + time1 / trialTimes + "ms\t自底向上:" + time2 / trialTimes + "ms"); n *= 10; } } } }
2.2.29
解答
完全有序時只需要一次歸並(直接輸出),
逆序時需要 n - 1 次歸並(退化為插入排序),
平均需要 n/2 次歸並。
所以分別需要 500,500000,500000000 次歸並。
span>Insertion: " + SortCompare.Time(insertionSort, arrayInsertion)); Console.WriteLine("Selection: " + SortCompare.Time(selectionSort, arraySelection)); Console.WriteLine("Shell: " + SortCompare.Time(shellSort, arrayShell)); // 泊松分布 arrayInsertion = SortCompare.GetPossionDistributionArray(n); arrayInsertion.CopyTo(arraySelection, 0); arrayInsertion.CopyTo(arrayShell, 0); Console.WriteLine("Poission Distribution:"); Console.WriteLine("Insertion: " + SortCompare.Time(insertionSort, arrayInsertion)); Console.WriteLine("Selection: " + SortCompare.Time(selectionSort, arraySelection)); Console.WriteLine("Shell: " + SortCompare.Time(shellSort, arrayShell)); // 幾何分布 arrayInsertion = SortCompare.GetGeometricDistributionArray(n, 0.3); arrayInsertion.CopyTo(arraySelection, 0); arrayInsertion.CopyTo(arrayShell, 0); Console.WriteLine("Geometric Distribution:"); Console.WriteLine("Insertion: " + SortCompare.Time(insertionSort, arrayInsertion)); Console.WriteLine("Selection: " + SortCompare.Time(selectionSort, arraySelection)); Console.WriteLine("Shell: " + SortCompare.Time(shellSort, arrayShell)); // 離散分布 arrayInsertion = SortCompare.GetDiscretDistributionArray(n, new double[] { 0.1, 0.2, 0.3, 0.1, 0.1, 0.1, 0.1 }); arrayInsertion.CopyTo(arraySelection, 0); arrayInsertion.CopyTo(arrayShell, 0); Console.WriteLine("Discret Distribution:"); Console.WriteLine("Insertion: " + SortCompare.Time(insertionSort, arrayInsertion)); Console.WriteLine("Selection: " + SortCompare.Time(selectionSort, arraySelection)); Console.WriteLine("Shell: " + SortCompare.Time(shellSort, arrayShell)); } } }
2.1.36
解答
最后結果:
代碼
using System; using Sort; namespace _2._1._36 { /* * 2.1.36 * * 不均勻的數據。 * 編寫一個測試用例, * 生成不均勻的測試數據,包括: * 一半數據是 0,一半數據是 1 * 一半數據是 0,1/4 是 1,1/4 是 2,以此類推 * 一半數據是 0,一半是隨機 int 值。 * 評估並驗證這些輸入數據對本節討論的算法的性能的影響。 * */ class Program { // 選擇排序的耗時與輸入值的內容無關,不受影響。 // 對於插入排序,以上幾種情況都是重復值較多的情況,插入排序的速度會加快。 // 希爾排序本質上也是插入排序,因此也會更快一些。 static void Main(string[] args) { int n = 10000; InsertionSort insertionSort = new InsertionSort(); SelectionSort selectionSort = new SelectionSort(); ShellSort shellSort = new ShellSort(); int[] arrayInsertion = new int[n]; int[] arraySelection = new int[n]; int[] arrayShell = new int[n]; // 對照,完全隨機 arrayInsertion = HalfZeroHalfOne(n); arrayInsertion.CopyTo(arraySelection, 0); arrayInsertion.CopyTo(arrayShell, 0); Console.WriteLine("totally random"); Console.WriteLine("Insertion Sort:" + SortCompare.TimeRandomInput(insertionSort, n, 1)); Console.WriteLine("Selection Sort:" + SortCompare.TimeRandomInput(selectionSort, n, 1)); Console.WriteLine("Shell Sort:" + SortCompare.TimeRandomInput(shellSort, n, 1)); Console.WriteLine(); // 一半是 0 一半是 1 arrayInsertion = HalfZeroHalfOne(n); arrayInsertion.CopyTo(arraySelection, 0); arrayInsertion.CopyTo(arrayShell, 0); Console.WriteLine("half 0 and half 1"); Console.WriteLine("Insertion Sort:" + SortCompare.Time(insertionSort, arrayInsertion)); Console.WriteLine("Selection Sort:" + SortCompare.Time(selectionSort, arraySelection)); Console.WriteLine("Shell Sort:" + SortCompare.Time(shellSort, arrayShell)); Console.WriteLine(); // 一半是 0, 1/4 是 1, 1/8 是 2…… arrayInsertion = HalfAndHalf(n); arrayInsertion.CopyTo(arraySelection, 0); arrayShell.CopyTo(arrayShell, 0); Console.WriteLine("half and half and half ..."); Console.WriteLine("Insertion Sort:" + SortCompare.Time(insertionSort, arrayInsertion)); Console.WriteLine("Selection Sort:" + SortCompare.Time(selectionSort, arraySelection)); Console.WriteLine("Shell Sort:" + SortCompare.Time(shellSort, arrayShell)); Console.WriteLine(); // 一半是 0,一半是隨機 int 值 arrayInsertion = HalfZeroHalfRandom(n); arrayInsertion.CopyTo(arraySelection, 0); arrayShell.CopyTo(arrayShell, 0); Console.WriteLine("half 0 half random"); Console.WriteLine("Insertion Sort:" + SortCompare.Time(insertionSort, arrayInsertion)); Console.WriteLine("Selection Sort:" + SortCompare.Time(selectionSort, arraySelection)); Console.WriteLine("Shell Sort:" + SortCompare.Time(shellSort, arrayShell)); } /// <summary> /// 獲取一半是 0 一半是 1 的隨機 <see cref="int"/> 數組。 /// </summary> /// <param name="n">數組大小。</param> /// <returns>一半是 0 一半是 1 的 <see cref="int"/>數組。</returns> static int[] HalfZeroHalfOne(int n) { int[] result = new int[n]; Random random = new Random(); for (int i = 0; i < n; i++) { if (random.NextDouble() >= 0.5) { result[i] = 0; } else { result[i] = 1; } } return result; } /// <summary> /// 生成 1/2 為 0, 1/4 為 1, 1/8 為 2 …… 的 <see cref="int"/> 數組。 /// </summary> /// <param name="n">數組長度。</param> /// <returns>1/2 為 0, 1/4 為 1, 1/8 為 2 …… 的 <see cref="int"/> 數組。</returns> static int[] HalfAndHalf(int n) { int[] array = new int[n]; HalfIt(0, 0, n / 2, array); Shuffle(array); return array; } /// <summary> /// 遞歸生成 1/2 為 0, 1/4 為 1, 1/8 為 2 …… 的 <see cref="int"/> 數組。 /// </summary> /// <param name="start">填充起點。</param> /// <param name="number">起始編號。</param> /// <param name="length">填充長度</param> /// <param name="array">用於填充的數組。</param> /// <returns>一個 <see cref="int"/> 數組。</returns> static int[] HalfIt(int start, int number, int length, int[] array) { if (length == 0) return array; for (int i = 0; i < length; i++) { array[start + i] = number; } return HalfIt(start + length, number + 1, length / 2, array); } /// <summary> /// 生成一半是 0 一半是隨機整數的 <see cref="int"/> 數組。 /// </summary> /// <param name="n">數組大小。</param> /// <returns>生成一半是 0 一半是隨機整數的 <see cref="int"/> 數組。</returns> static int[] HalfZeroHalfRandom(int n) { int[] array = new int[n]; Random random = new Random(); for (int i = 0; i < n / 2; i++) { array[i] = 0; } for (int i = n / 2; i < n; i++) { array[i] = random.Next(); } Shuffle(array); return array; } /// <summary> /// 打亂數組。 /// </summary> /// <param name="a">需要打亂的數組。</param> static void Shuffle(int[] a) { int N = a.Length; Random random = new Random(); for (int i = 0; i < N; i++) { int r = i + random.Next(N - i);// 等於StdRandom.uniform(N-i) int temp = a[i]; a[i] = a[r]; a[r] = temp; } } } }
2.1.37
解答
主要說一下第二個的實現,把一個數組循環左移/右移幾位即可。
代碼
using System; using System.Collections.Generic; using Sort; namespace _2._1._37 { /* * 2.1.37 * * 部分有序。 * 編寫一個測試用例,生成部分有序數組,包括: * 95% 有序,其余部分為隨機值。 * 所有元素和它們的正確位置的距離都不超過 10。 * 5% 的元素隨機分布在整個數組中,剩下的數據都是有序的。 * 評估並驗證這些輸入數據對本節討論的算法的性能的影響。 * */ class Program { // 選擇排序的性能只與數組大小有關,以上三種情況耗時都是近似的。 // 插入排序的性能與逆序對數量有關,部分有序的情況下耗時會小於完全隨機。 // 希爾排序與插入排序類似。 static void Main(string[] args) { InsertionSort insertionSort = new InsertionSort(); SelectionSort selectionSort = new SelectionSort(); ShellSort shellSort = new ShellSort(); int n = 10000; int[] selectionArray = new int[n]; int[] insertionArray = new int[n]; int[] shellArray = new int[n]; // 完全隨機的對照 Console.WriteLine("totally random"); Console.WriteLine("Selection Sort:" + SortCompare.TimeRandomInput(selectionSort, n, 1)); Console.WriteLine("Insertion Sort:" + SortCompare.TimeRandomInput(insertionSort, n, 1)); Console.WriteLine("Shell Sort:" + SortCompare.TimeRandomInput(shellSort, n, 1)); // 95% 有序,其余部分為隨機值。 selectionArray = Sorted95Random5(n); selectionArray.CopyTo(insertionArray, 0); selectionArray.CopyTo(shellArray, 0); Console.WriteLine("95% sorted + 5% random"); Console.WriteLine("Selection Sort:" + SortCompare.Time(selectionSort, selectionArray)); Console.WriteLine("Insertion Sort:" + SortCompare.Time(insertionSort, insertionArray)); Console.WriteLine("Shell Sort:" + SortCompare.Time(shellSort, shellArray)); // 所有元素和它們的正確位置的距離都不超過 10。 selectionArray = RandomIn10(n); selectionArray.CopyTo(insertionArray, 0); selectionArray.CopyTo(shellArray, 0); Console.WriteLine("a sorted array that left shift 6 times"); Console.WriteLine("Selection Sort:" + SortCompare.Time(selectionSort, selectionArray)); Console.WriteLine("Insertion Sort:" + SortCompare.Time(insertionSort, insertionArray)); Console.WriteLine("Shell Sort:" + SortCompare.Time(shellSort, shellArray)); // 5% 的元素隨機分布在整個數組中,剩下的數據都是有序的。 selectionArray = RandomIn10(n); selectionArray.CopyTo(insertionArray, 0); selectionArray.CopyTo(shellArray, 0); Console.WriteLine("95% elements is sorted while 5% elements are placed randomly"); Console.WriteLine("Selection Sort:" + SortCompare.Time(selectionSort, selectionArray)); Console.WriteLine("Insertion Sort:" + SortCompare.Time(insertionSort, insertionArray)); Console.WriteLine("Shell Sort:" + SortCompare.Time(shellSort, shellArray)); } /// <summary> /// 生成 95% 有序,最后 5% 隨機的 <see cref="int"/> 數組。 /// </summary> /// <param name="n">數組的大小。</param> /// <returns>95% 有序,最后 5% 隨機的 <see cref="int"/> 數組。</returns> static int[] Sorted95Random5(int n) { int[] array = new int[n]; int randomStart = (int)(n * 0.05); Random random = new Random(); for (int i = 0; i < n - randomStart; i++) { array[i] = i; } for (int i = n - randomStart; i < n; i++) { array[i] = random.Next(); } return array; } /// <summary> /// 返回一個 <see cref="int"/> 數組,其中的每個元素和它的正確位置的距離都不超過 10。 /// </summary> /// <param name="n">數組大小。</param> /// <returns>一個 <see cref="int"/> 數組,其中的每個元素和它的正確位置的距離都不超過 10。</returns> static int[] RandomIn10(int n) { Queue<int> array = new Queue<int>(); Random random = new Random(); for (int i = 0; i < n; i++) { array.Enqueue(i); } for (int i = 0; i < 6; i++) { array.Enqueue(array.Dequeue()); } return array.ToArray(); } /// <summary> /// 生成 5% 元素隨機分布,剩余有序的 <see cref="int"/> 數組。 /// </summary> /// <param name="n">需要生成的數組大小。</param> /// <returns>5% 元素隨機分布,剩余有序的 <see cref="int"/> 數組。</returns> static int[] Shuffle5Percent(int n) { Random random = new Random(); int percent5 = (int)(n * 0.05); int[] randomIndex = new int[percent5]; for (int i = 0; i < percent5; i++) { randomIndex[i] = random.Next(percent5); } int[] randomValue = new int[percent5]; for (int i = 0; i < percent5; i++) { randomValue[i] = randomIndex[i]; } Shuffle(randomValue); int[] array = new int[n]; for (int i = 0; i < n; i++) { array[i] = i; } for (int i = 0; i < percent5; i++) { array[randomIndex[i]] = randomValue[i]; } return array; } /// <summary> /// 打亂數組。 /// </summary> /// <param name="a">需要打亂的數組。</param> static void Shuffle(int[] a) { int N = a.Length; Random random = new Random(); for (int i = 0; i < N; i++) { int r = i + random.Next(N - i);// 等於StdRandom.uniform(N-i) int temp = a[i]; a[i] = a[r]; a[r] = temp; } } } }
2.1.38
解答
這里實現了一個 Pair 類,用來排序。
每一個元素都有相應的 key 值和 value 值,排序時只使用 key 值進行排序。
代碼
using System; using Sort; namespace _2._1._38 { /* * 2.1.38 * * 不同類型的元素。 * 編寫一個測試用例,生成由多種數據類型元素組成的數組,元素的主鍵值隨機,包括: * 每個元素的主鍵均為 String 類型(至少長 10 個字符),並含有一個 double 值。 * 每個元素的主鍵均為 double 類型,並含有 10 個 String 值(每個都至少長 10 個字符)。 * 每個元素的主鍵均為 int 類型,並含有一個 int[20] 值。 * 評估並驗證這些輸入數據對本節討論的算法的性能的影響。 * */ class Program { static void Main(string[] args) { int n = 10000; double[] results = TestA(n); Console.WriteLine("string + double"); Console.WriteLine("Insertion Sort:" + results[0]); Console.WriteLine("Selection Sort:" + results[1]); Console.WriteLine("Shell Sort:" + results[2]); results = TestB(n); Console.WriteLine("double + 10 string"); Console.WriteLine("Insertion Sort:" + results[0]); Console.WriteLine("Selection Sort:" + results[1]); Console.WriteLine("Shell Sort:" + results[2]); results = TestC(n); Console.WriteLine("int + int[]"); Console.WriteLine("Insertion Sort:" + results[0]); Console.WriteLine("Selection Sort:" + results[1]); Console.WriteLine("Shell Sort:" + results[2]); } /// <summary> /// 第一個測試,測試結果按照 Insertion, Selection, Shell 排序。 /// </summary> /// <param name="n">測試的數組長度。</param> /// <returns>測試結果。</returns> static double[] TestA(int n) { InsertionSort insertionSort = new InsertionSort(); SelectionSort selectionSort = new SelectionSort(); ShellSort shellSort = new ShellSort(); Random random = new Random(); // 每個元素的主鍵均為 String 類型(至少長 10 個字符),並含有一個 double 值。 Pair<string, double>[] array = new Pair<string, double>[n]; Pair<string, double>[] arrayBak = new Pair<string, double>[n]; for (int i = 0; i < n; i++) { array[i] = new Pair<string, double>(RandomString(20, random), random.NextDouble()); } array.CopyTo(arrayBak, 0); double[] results = new double[3]; results[0] = SortCompare.Time(insertionSort, array); arrayBak.CopyTo(array, 0); results[1] = SortCompare.Time(selectionSort, array); arrayBak.CopyTo(array, 0); results[2] = SortCompare.Time(shellSort, array); return results; } /// <summary> /// 第二個測試,測試結果按照 Insertion, Selection, Shell 排序。 /// </summary> /// <param name="n">測試的數組長度。</param> /// <returns>測試結果。</returns> static double[] TestB(int n) { InsertionSort insertionSort = new InsertionSort(); SelectionSort selectionSort = new SelectionSort(); ShellSort shellSort = new ShellSort(); Random random = new Random(); // 每個元素的主鍵均為 double 類型,並含有 10 個 String 值(每個都至少長 10 個字符)。 Pair<double, string[]>[] array = new Pair<double, string[]>[n]; Pair<double, string[]>[] arrayBak = new Pair<double, string[]>[n]; for (int i = 0; i < n; i++) { string[] temp = new string[10]; for (int j = 0; j < 10; j++) { temp[j] = RandomString(12, random); } array[i] = new Pair<double, string[]>(random.NextDouble(), temp); } array.CopyTo(arrayBak, 0); double[] results = new double[3]; results[0] = SortCompare.Time(insertionSort, array); arrayBak.CopyTo(array, 0); results[1] = SortCompare.Time(selectionSort, array); arrayBak.CopyTo(array, 0); results[2] = SortCompare.Time(shellSort, array); return results; } /// <summary> /// 第三個測試,測試結果按照 Insertion, Selection, Shell 排序。 /// </summary> /// <param name="n">測試的數組長度。</param> /// <returns>測試結果。</returns> static double[] TestC(int n) { InsertionSort insertionSort = new InsertionSort(); SelectionSort selectionSort = new SelectionSort(); ShellSort shellSort = new ShellSort(); Random random = new Random(); // 每個元素的主鍵均為 int 類型,並含有一個 int[20] 值。 Pair<int, int[]>[] array = new Pair<int, int[]>[n]; Pair<int, int[]>[] arrayBak = new Pair<int, int[]>[n]; for (int i = 0; i < n; i++) { int[] temp = new int[20]; for (int j = 0; j < 20; j++) { temp[j] = random.Next(); } array[i] = new Pair<int, int[]>(random.Next(), temp); } array.CopyTo(arrayBak, 0); double[] results = new double[3]; results[0] = SortCompare.Time(insertionSort, array); arrayBak.CopyTo(array, 0); results[1] = SortCompare.Time(selectionSort, array); arrayBak.CopyTo(array, 0); results[2] = SortCompare.Time(shellSort, array); return results; } /// <summary> /// 獲取一個隨機 <see cref="string"/>。 /// </summary> /// <param name="n"><see cref="string"/> 的長度。</param> /// <param name="random">隨機數生成器。</param> /// <returns>獲取一個隨機 <see cref="string"/>。</returns> static string RandomString(int n, Random random) { char[] value = new char[n]; for (int i = 0; i < n; i++) { value[i] = (char)random.Next(char.MinValue + 10, char.MaxValue - 10); } return new string(value); } } }
a = ReadAllInts(TestCase.Properties.Resources._1Mints); linkedTime = TimeTrialLinkedStack(a); arrayTime = TimeTrialDoublingStack(a); Console.WriteLine($"1000000\t{linkedTime}\t{arrayTime}\t{linkedTime / arrayTime}"); } } }
1.4.44
解答
每生成一個隨機數都和之前生成過的隨機數相比較。
代碼
using System; namespace _1._4._44 { /* * 1.4.44 * * 生日問題。 * 編寫一個程序, * 從命令行接受一個整數 N 作為參數並使用 StdRandom.uniform() 生成一系列 0 到 N-1 之間的隨機整數。 * 通過實驗驗證產生第一個重復的隨機數之前生成的整數數量為 ~√(πN/2)。 * */ class Program { static void Main(string[] args) { Random random = new Random(); int N = 10000; int[] a = new int[N]; int dupNum = 0; int times = 0; for (times = 0; times < 500; ++times) { for (int i = 0; i < N; ++i) { a[i] = random.Next(N); if (IsDuplicated(a, i)) { dupNum += i; Console.WriteLine($"生成{i + 1}個數字后發生重復"); break; } } } Console.WriteLine($"√(πN/2)={Math.Sqrt(Math.PI * N / 2.0)},平均生成{dupNum / times}個數字后出現重復"); } /// <summary> /// 檢查是否有重復的數字出現。 /// </summary> /// <param name="a">需要檢查的數組。</param> /// <param name="i">當前加入數組元素的下標。</param> /// <returns>有重復則返回 true,否則返回 false。</returns> static bool IsDuplicated(int[] a, int i) { for (int j = 0; j < i; ++j) { if (a[j] == a[i]) { return true; } } return false; } } }
1.4.45
解答
建立一個布爾數組,將每次隨機出來的數作為下標,將相應位置的布爾值改為 true,每次隨機都檢查一遍這個數組是否都是 true。
代碼
using System; namespace _1._4._45 { /* * 1.4.45 * * 優惠券收集問題。 * 用和上一題相同的方式生成隨機整數。 * 通過實驗驗證生成所有可能的整數值所需生成的隨機數總量為 ~NHN。 * (這里的 HN 中 N 是下標) * */ class Program { // HN 指的是調和級數 static void Main(string[] args) { Random random = new Random(); int N = 10000; bool[] a = new bool[N]; int randomSize = 0; int times = 0; for (times = 0; times < 20; ++times) { for (int i = 0; i < N; ++i) { a[i] = false; } for(int i = 0; true; ++i) { int now = random.Next(N); a[now] = true; if (IsAllGenerated(a)) { randomSize += i; Console.WriteLine($"生成{i}次后所有可能均出現過了"); break; } } } Console.WriteLine($"\nNHN={N * HarmonicSum(N)},平均生成{randomSize / times}個數字后所有可能都出現"); } /// <summary> /// 計算 N 階調和級數的和。 /// </summary> /// <param name="N">調和級數的 N 值</param> /// <returns>N 階調和級數的和。</returns> static double HarmonicSum(int N) { double sum = 0; for (int i = 1; i <= N; ++i) { sum += 1.0 / i; } return sum; } /// <summary> /// 檢查所有數字是否都生成過了。 /// </summary> /// <param name="a">布爾數組。</param> /// <returns>全都生成則返回 true,否則返回 false。</returns> static bool IsAllGenerated(bool[] a) { foreach (bool i in a) { if (!i) return false; } return true; } } }
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; } }















