歸併排序
歸併排序也是採用“分而治之”的方式。剛發現分治法是一種算法範式,我還一直以爲是一種需要意會的思想呢。
不好意思了,孤陋寡聞了,哈哈!
原理:將兩個有序的數列,通過比較,合併爲一個有序數列。 維基入口
爲方便理解,此處實現用了List<int>的一些方法,隨後有IList<int>版本。
實現如下:
public static List<int> MergeSortOnlyList(List<int> data, int low, int high) { if (low == high) return new List<int> { data[low] }; List<int> mergeData = new List<int>(); int mid = (low + high) / 2; List<int> leftData = MergeSortOnlyList(data, low, mid); List<int> rightData = MergeSortOnlyList(data, mid + 1, high); int i = 0, j = 0; while (true) { if (leftData[i] < rightData[j]) { mergeData.Add(leftData[i]); if (++i == leftData.Count) { mergeData.AddRange(rightData.GetRange(j, rightData.Count - j)); break; } } else { mergeData.Add(rightData[j]); if (++j == rightData.Count) { mergeData.AddRange(leftData.GetRange(i, leftData.Count - i)); break; } } } return mergeData; } public static List<int> MergeSortOnlyList(List<int> data) { data = MergeSortOnlyList(data, 0, data.Count - 1); //不會改變外部引用 參照C#參數傳遞 return data; }
過程解析:將數列分爲兩部分,分別得到兩部分數列的有序版本,然後逐個比較,將比較出的小數逐個放進
新的空數列中。當一個數列放完後,將另一個數列剩餘數全部放進去。
IList<int>版本
實現如下:
public static IList<int> MergeSort(IList<int> data) { data = MergeSort(data, 0, data.Count - 1); return data; } public static IList<int> MergeSort(IList<int> data, int low, int high) { int length = high - low + 1; IList<int> mergeData = NewInstance(data, length); if (low == high) { mergeData[0] = data[low]; return mergeData; } int mid = (low + high) / 2; IList<int> leftData = MergeSort(data, low, mid); IList<int> rightData = MergeSort(data, mid + 1, high); int i = 0, j = 0; while (true) { if (leftData[i] < rightData[j]) { mergeData[i + j] = leftData[i++]; //不能使用Add,Array Length不可變 if (i == leftData.Count) { int rightLeft = rightData.Count - j; for (int m = 0; m < rightLeft; m++) { mergeData[i + j] = rightData[j++]; } break; } } else { mergeData[i + j] = rightData[j++]; if (j == rightData.Count) { int leftleft = leftData.Count - i; for (int n = 0; n < leftleft; n++) { mergeData[i + j] = leftData[i++]; } break; } } } return mergeData; }
過程原理與上個一樣,此處就不贅述了。
堆排序
堆排序是根據堆這種數據結構設計的一種算法。堆的特性:父節點的值總是小於(或大於)它的子節點。近似二叉樹。
原理:將數列構建爲最大堆數列(即父節點總是最大值),將最大值(即根節點)交換到數列末尾。這樣要排序的數列數總和減少,
同時根節點不再是最大值,調整最大堆數列。如此重複,最後得到有序數列。 維基入口 有趣的演示
實現準備:如何將數列構造爲堆——父節點i的左子節點爲2i+1,右子節點爲2i+2。節點i的父節點爲floor((i-1)/2)。
實現如下(這個實現判斷和臨時變量使用太多,導致效率低,評論中@小城故事提出了更好的實現):
public static void HeapSort(IList<int> data) { BuildMaxHeapify(data); int j = data.Count; for (int i = 0; i < j; ) { Swap(data, i, --j); if (j - 2 < 0) //只剩下1個數 j代表餘下要排列的數的個數 break; int k = 0; while (true) { if (k > (j - 2) / 2) break; //即:k > ((j-1)-1)/2 超出最後一個父節點的位置 else { int temp = k; k = ReSortMaxBranch(data, k, 2 * k + 1, 2 * k + 2, j - 1); if (temp == k) break; } } } } public static void BuildMaxHeapify(IList<int> data) { for (int i = data.Count / 2 - 1; i >= 0; i--) //(data.Count-1)-1)/2爲數列最大父節點索引 { int temp = i; temp = ReSortMaxBranch(data, i, 2 * i + 1, 2 * i + 2, data.Count - 1); if (temp != i) { int k = i; while (k != temp && temp <= data.Count / 2 - 1) { k = temp; temp = ReSortMaxBranch(data, temp, 2 * temp + 1, 2 * temp + 2, data.Count - 1); } } } } public static int ReSortMaxBranch(IList<int> data, int maxIndex, int left, int right, int lastIndex) { int temp; if (right > lastIndex) //父節點只有一個子節點 temp = left; else { if (data[left] > data[right]) temp = left; else temp = right; } if (data[maxIndex] < data[temp]) Swap(data, maxIndex, temp); else temp = maxIndex; return temp; }
過程解析:BuildMaxHeapify爲排序前構建的最大堆數列方法,主要內容爲從最後一個父節點開始往前將每個三角組合
(即父節點與它的兩個子節點)符合父節點值最大的規則。ReSortMaxBranch爲將三角調整爲父節點值最大,
並返回該值之前的索引,用來判斷是否進行了交換,以及原來的父節點值交換到了什麼位置。在HeapSort裏首先
構建了最大堆數列,然後將根節點交換到末尾,根節點不是最大值了,在while語句中對最大堆數列進行調整。
插曲:自從看了Martin Fowler大師《重構》第三版,我發現我更不喜歡寫註釋了。每次都想着儘量讓方法的名字更貼切,
即使會造成方法的名字很長很醜。這算不算曲解了大師的意思啊!?上面的代碼註釋都是寫博客的時候現加的(源代碼很乾淨的。汗!)。
希爾排序
希爾排序是插入排序的一種更高效的改進版本。
在前面介紹的插入排序,我們知道1.它對有序數列排序的效率是非常高的 2.要排序的數向前移動是一步步進行的導致插入排序效率低。
希爾排序正是利用第一點,改善第二點,達到更理想的效果。
原理:通過奇妙的步長,插入排序間隔步長的元素,隨後逐漸縮短步長至1,實現數列的插入排序。 維基入口
疑問:可以想象到排序間隔步長的數,會逐漸讓數列變得有序,提升最後步長爲1時標準插入排序的效率。在維基上看到這麼
一句話“可能希爾排序最重要的地方在於當用較小步長排序後,以前用的較大步長仍然是有序的”注意用詞是‘可能’。我的疑問是
這是個正確的命題嗎?如何證明呢?看維基上也是由果推因,說是如果不是這樣,就不會排序那麼快了。可這我感覺還是太牽強了,
哪位大哥發現相關資料,希望能分享出來,不勝感激。
實現如下:
public static void ShellSortCorrect(IList<int> data) { int temp; for (int gap = data.Count / 2; gap > 0; gap /= 2) { for (int i = gap; i < data.Count; i++) // i+ = gap 改爲了 i++ { temp = data[i]; for (int j = i - gap; j >= 0; j -= gap) { if (data[j] > temp) { data[j + gap] = data[j]; if (j == 0) { data[j] = temp; break; } } else { data[j + gap] = temp; break; } } } } }
基數排序
基數排序是一種非比較型整數排序。
“非比較型”是什麼意思呢?因爲它內部使用的是桶排序,而桶排序是非比較型排序。
這裏就要說說桶排序了。一個非常有意思的排序。
桶排序
原理:取一定數量(數列中的最大值)的編好序號的桶,將數列每個數放進編號爲它的桶裏,然後將不是空的桶依次倒出來,
就組成有序數列了。 維基入口
好吧!聰明的人一眼就看出桶排序的破綻了。假設只有兩個數1,10000,豈不是要一萬個桶!?這確實是個問題啊!我也
沒想出解決辦法。我起初也以爲桶排序就是一個通過犧牲空間來換取時間的排序算法,它不需要比較,所以是非比較型算法。
但看了有趣的演示的桶排序後,發現世界之大,你沒有解決,不代表別人沒解決,睿智的人總是很多。
1,9999的桶排序實現:new Int[2];總共有兩個數,得出最大數9999的位數4,取10的4次冪即10000作爲分母,
要排序的數(1或9999)作爲分子,並乘以數列總數2,即1*2/10000,9999*2/10000得到各自的位置0,1,完成排序。
如果是1,10000進行排序的話,上面的做法就需要稍微加一些處理——發現最大數是10的n次冪,就將它作爲分母,並
放在數列末尾就好了。
如果是9999,10000進行排序的話,那就需要二維數組了,兩個都在位置1,位置0沒數。這個時候就需要在放
入每個位置時採用其它排序(比如插入排序)辦法對這個位置的多個數排序了。
爲基數排序做個過渡,我這裏實現了一個個位數桶排序
涉及到了當重複的數出現的處理。
實現如下:
public static void BucketSortOnlyUnitDigit(IList<int> data) { int[] indexCounter = new int[10]; for (int i = 0; i < data.Count; i++) { indexCounter[data[i]]++; } int[] indexBegin = new int[10]; for (int i = 1; i < 10; i++) { indexBegin[i] = indexBegin[i-1]+ indexCounter[i-1]; } IList<int> tempList = NewInstance(data, data.Count); for (int i = 0; i < data.Count; i++) { int number = data[i]; tempList[indexBegin[number]++] = data[i]; } data = tempList; }
過程解析:indexCounter進行對每個數出現的頻率的統計。indexBegin存儲每個數的起始索引。
比如 1 1 2,indexCounter統計到0個0,2個1,1個2。indexBegin計算出0,1,2的起始索引分別爲
0,0,2。當1個1已取出排序,那索引將+1,變爲0,1,2。這樣就通過提前給重複的數空出位置,解決了
重複的數出現的問題。當然,你也可以考慮用二維數組來解決重複。
下面繼續基數排序。
基數排序原理:將整數按位數切割成不同的數字,然後按每個位數分別比較。
取得最大數的位數,從低位開始,每個位上進行桶排序。
實現如下:
public static IList<int> RadixSort(IList<int> data) { int max = data[0]; for (int i = 1; i < data.Count; i++) { if (data[i] > max) max = data[i]; } int digit = 1; while (max / 10 != 0) { digit++; max /= 10; } for (int i = 0; i < digit; i++) { int[] indexCounter = new int[10]; IList<int> tempList = NewInstance(data, data.Count); for (int j = 0; j < data.Count; j++) { int number = (data[j] % Convert.ToInt32(Math.Pow(10, i + 1))) / Convert.ToInt32(Math.Pow(10, i)); //得出i+1位上的數 indexCounter[number]++; } int[] indexBegin = new int[10]; for (int k = 1; k < 10; k++) { indexBegin[k] = indexBegin[k - 1] + indexCounter[k - 1]; } for (int k = 0; k < data.Count; k++) { int number = (data[k] % Convert.ToInt32(Math.Pow(10, i + 1))) / Convert.ToInt32(Math.Pow(10, i)); tempList[indexBegin[number]++] = data[k]; } data = tempList; } return data; }
過程解析:得出最大數的位數,從低位開始桶排序。我寫的這個實現代碼並不簡潔,但邏輯更清晰。
後面測試的時候我們就會發現,按理來說這個實現也還行吧! 但並不如想象的那麼快!
循環的次數太多?(統計頻率n次+9次計算+n次放到新的數組)*位數。
創建的新實例太多?(new int[10]兩次+NewInstance is反射判斷創建實例+new int[n])*位數
測試比較
添加隨機數組,數組有序校驗,微軟Linq排序
代碼如下:
public static int[] RandomSet(int length, int max) { int[] result = new int[length]; Random rand = new Random(); for (int i = 0; i < result.Length; i++) { result[i] = rand.Next(max); } return result; } public static bool IsAscOrdered(IList<int> data) { bool flag = true; for (int i = 0; i < data.Count - 1; i++) { if (data[i] > data[i + 1]) flag = false; } return flag; } public static void TestMicrosoft(IList<int> data) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); List<int> result = data.OrderBy(a => a).ToList(); stopwatch.Stop(); string methodName = "TestMicrosoft"; int length = methodName.Length; for (int i = 0; i < 40 - length; i++) { methodName += " "; } Console.WriteLine(methodName + " IsAscOrdered:" + IsAscOrdered(result) + " Time:" + stopwatch.Elapsed.TotalSeconds); }
測試主體如下:
static void Main(string[] args) { int[] aa = RandomSet(50000, 99999); //int[] aa = OrderedSet(5000); Console.WriteLine("Array Length:" + aa.Length); RunTheMethod((Action<IList<int>>)SelectSort, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)BubbleSort, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)BubbleSortImprovedWithFlag, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)BubbleCocktailSort, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)InsertSort, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)InsertSortImprovedWithBinarySearch, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)QuickSortStrict, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)QuickSortRelax, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)QuickSortRelaxImproved, aa.Clone() as int[]); RunTheMethod((Func<IList<int>, IList<int>>)MergeSort, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)ShellSort, aa.Clone() as int[]); RunTheMethod((Func<IList<int>, IList<int>>)RadixSort, aa.Clone() as int[]); RunTheMethod((Action<IList<int>>)HeapSort, aa.Clone() as int[]); TestMicrosoft(aa.Clone() as int[]); Console.Read(); } public static void RunTheMethod(Func<IList<int>, IList<int>> method, IList<int> data) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); IList<int> result = method(data); stopwatch.Stop(); string methodName = method.Method.Name; int length = methodName.Length; for (int i = 0; i < 40 - length; i++) { methodName += " "; } Console.WriteLine(methodName + " IsAscOrdered:" + IsAscOrdered(result) + " Time:" + stopwatch.Elapsed.TotalSeconds); } public static void RunTheMethod(Action<IList<int>> method, IList<int> data) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); method(data); stopwatch.Stop(); string methodName = method.Method.Name; int length = methodName.Length; for (int i = 0; i < 40 - length; i++) { methodName += " "; } Console.WriteLine(methodName + " IsAscOrdered:" + IsAscOrdered(data) + " Time:" + stopwatch.Elapsed.TotalSeconds); }
剩餘代碼摺疊在此處
public static void Swap(IList<int> data, int a, int b) { int temp = data[a]; data[a] = data[b]; data[b] = temp; } public static int[] OrderedSet(int length) { int[] result = new int[length]; for (int i = 0; i < length; i++) { result[i] = i; } return result; } public static IList<int> NewInstance(IList<int> data, int length) { IList<int> instance; if (data is Array) { instance = new int[length]; } else { instance = new List<int>(length); for (int n = 0; n < length; n++) { instance.Add(0); // 初始添加 } } return instance; }