1. 程式人生 > >8種主要排序演算法的C#實現 (二)

8種主要排序演算法的C#實現 (二)


歸併排序

歸併排序也是採用“分而治之”的方式。剛發現分治法是一種演算法正規化,我還一直以為是一種需要意會的思想呢。

不好意思了,孤陋寡聞了,哈哈!

原理:將兩個有序的數列,通過比較,合併為一個有序數列。 維基入口

為方便理解,此處實現用了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<
            
           

相關推薦

8主要排序演算法C#實現

歸併排序 歸併排序也是採用“分而治之”的方式。剛發現分治法是一種演算法正規化,我還一直以為是一種需要意會的思想呢。 不好意思了,孤陋寡聞了,哈哈! 原理:將兩個有序的數列,通過比較,合併為一個有序數列。 維基入口 為方便理解,此處實現用了List<in

7排序演算法C++實現標頭檔案

終於把這幾種排序演算法寫好啦,哈哈哈,記錄一下標頭檔案。#include<iostream> #include"func.h" using namespace std; class sort { public: void bubblesort(int *a,

常見的幾排序演算法以及實現C語言

所有未排序的陣列是經過檢查合法的 主要的內排序包括冒泡、插入、希爾、堆排序、歸併、快速、桶排序等 其C語言實現的原始檔下載地址:http://download.csdn.net/detail/mcu_tian/9530227 氣泡排序 氣泡排序應該是排序中最簡單的演算法了

排序演算法C++實現

七種內排序演算法,目前只寫了程式碼,原理解析待補充: 1.交換類排序:冒泡、快排; 2.選擇類排序:選擇、堆排序; 3.插入類排序:直接插入、希爾; 4.歸併排序 測試用例: 5 1 2 4 3 -3 10 100 293 123 212 293 434 5

深度剖析八大經典排序演算法C++實現通俗易懂版

內容會持續更新,有錯誤的地方歡迎指正,謝謝! 引言 需握各種排序和常用的資料結構的核心思想,並知道是以什麼樣的方式解決了什麼樣的問題。 該部落格的示例程式碼均以遞增排序為目的~ 學習建議:切忌看示例程式碼去理解演算法,而是理解演算法原理寫出程式碼,否

常見排序演算法C++實現冒泡,直接插入,希爾,堆,歸併,簡單選擇,快排

常見的排序演算法:氣泡排序、直接插入、希爾排序,堆排序,簡單選擇排序,快速排序。這些最基本的演算法都是應該熟練掌握的,下面是C++實現的程式碼,方便大家學習參考: #include<iostream> using namespace std; void swa

快速排序演算法實現遞迴演算法、非遞迴演算法、三路劃分快速排序

快速排序的三個步驟: 1、分解:將陣列A[l...r]劃分成兩個(可能空)子陣列A[l...p-1]和A[p+1...r],使得A[l...p-1]中的每個元素都小於等於A(p),而且,小於等於A[p

python常見排序演算法實現

在Python程式設計的實踐中,我們往往會遇到排序問題,比如在對搜尋引擎搜尋結果的排序(沒有排序就沒有Google、baidu等搜尋引擎的存在),當然,這樣的例子數不勝數。我大學中的必修課程《資料結構》

有限元剛度矩陣的一維變頻寬儲存用C++實現

我們接著上一篇有限元剛度矩陣的一維變頻寬儲存用C++實現(一)介紹。上一篇中,我們得到了輔助陣列pDiag中儲存的是總體剛度矩陣[K]每行的半頻寬。經過上一篇中節點自由度重編號,總剛矩陣[K]形式為: 此時,所有的非零元素都集中在帶內。 一

淺談排序演算法的效率Java筆記

首先:咱也借用一下網上的那張XXX的圖,咯!在下面: 接下來,就是咱的驗證時間了(驗證什麼?當然是各種演算法的時間複雜度比較咯),沒什麼好說的了,直接上碼吧。 程式碼實現: import java.util.Arrays; public class SortSum

Spring原始碼分析之IOC的三常見用法及原始碼實現

Spring原始碼分析之IOC的三種常見用法及原始碼實現(二) 回顧上文 我們研究的是 AnnotationConfigApplicationContext annotationConfigApplication = new AnnotationConfigApplicationContext

Java常用的八排序演算法與程式碼實現:歸併排序法、快速排序

注:這裡給出的程式碼方案都是通過遞迴完成的 --- 歸併排序(Merge Sort):   分而治之,遞迴實現   如果需要排序一個數組,我們先把陣列從中間分成前後兩部分,然後對前後兩部分進行分別排序,再將排好序的數組合並在一起,這樣整個陣列就有序了   歸併排序是穩定的排序演算法,時間

【資料結構】十一排序演算法C++實現

   練習了十一種排序演算法的C++實現:以下依次為,冒泡、選擇、希爾、插入、二路歸併、快排、堆排序、計數排序、基數排序、桶排序,可建立sort.h和main.cpp將程式碼放入即可執行。如有錯誤,請指出更正,謝謝交流。 // sort.h # include <

十大排序演算法及其實現C++ & Python

經典的幾大排序演算法,網上各種版本程式碼質量層次不齊。在此想自己做個總結,一方面希望通過這次總結加深自己對幾種排序演算法的認識和記憶,另一方面也希望能寫下來與大家分享。 每個演算法力求給出普通解法和最優解法,當前部分排序演算法還沒有給出最優解,待後續的

演算法》第四版algs4:sort排序演算法C++實現

具體程式碼: https://github.com/Nwpuer/algs4-in-cpp/blob/master/sort.h 這一章的實現,相比於書上我做了輕微的改變,主要目的是把程式碼寫的更加簡潔易懂,更加關注演算法是如何實現的,換言之,更關注演算法的本質,而不是如何去設計一個C+

經典排序演算法實現選擇,插入,shell,堆,快速,歸併排序

1.選擇排序 //選擇排序 void selectSort(int * arr, int n) { for (int i = 0; i < n - 1; i++) { int min = arr[i]; int minPos = i; for (int j = i

常用排序演算法C++實現

#ifndef SORT_H #define SORT_H class Sort { private: Sort(); Sort(const Sort&); Sort& operator = (const Sort&); te

排序演算法-c實現

#include <stdio.h> #include <stdlib.h> #include<cstring> void quicksort(int arr[],int left,int right) { if (left > r

連結串列排序演算法java實現連結串列的快速排序、插入排序、歸併排序

難易程度:★★ 重要性:★★★      連結串列的排序相對陣列的排序更為複雜些,也是考察求職者是否真正理解了排序演算法(而不是“死記硬背”) 連結串列的插入排序 public class LinkedInsertSort { static cla