轉載 C#排序算法

轉載 http://www.codeisbug.com/Doc/6

- 希爾排序

希爾排序是將組分段,進行插入排序.
對想提高C#語言編程能力的朋友,我們可以互相探討一下。
如:下面的程序,並沒有實現多態,來,幫它實現一下。

using System;
public class ShellSorter
{
    public void Sort(int[] list)
    {
        int inc;
        for (inc = 1; inc <= list.Length / 9; inc = 3 * inc + 1) ;
        for (; inc > 0; inc /= 3)
        {
            for (int i = inc + 1; i <= list.Length; i += inc)
            {
                int t = list[i - 1];
                int j = i;
                while ((j > inc) && (list[j - inc - 1] > t))
                {
                    list[j - 1] = list[j - inc - 1];
                    j -= inc;
                }
                list[j - 1] = t;
            }
        }
    }
}
public class MainClass
{
    public static void Main()
    {
        int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
        ShellSorter sh = new ShellSorter();
        sh.Sort(iArrary);
        for (int m = 0; m <= 13; m++)
            Console.WriteLine("{0}", iArrary[m]);
    }
}

- 插入排序

輸入一個元素,檢查數組列表中的每個元素,將其插入到一個已經排好序的數列中的適當位置,使數列依然有序,當最後一個元素放入合適位置時,該數組排序完畢。

using System;
public class InsertionSorter
{
    public void Sort(int[] list)
    {
        for (int i = 1; i < list.Length; ++i)
        {
            int t = list[i];
            int j = i;
            while ((j > 0) && (list[j - 1] > t))
            {
                list[j] = list[j - 1];
                --j;
            }
            list[j] = t;
        }
    }
}
public class MainClass
{
    public static void Main()
    {
        int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
        InsertionSorter ii = new InsertionSorter();
        ii.Sort(iArrary);
        for (int m = 0; m <= 13; m++)
            Console.WriteLine("{0}", iArrary[m]);
    }
}

- 選擇排序

選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工作原理是每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的數據元素排完。 選擇排序是不穩定的排序方法(比如序列[5, 5, 3]第一次就將第一個[5]與[3]交換,導致第一個5挪動到第二個5後面)。

選擇排序是不穩定的。算法複雜度O(n2)–[n的平方]

using System;
public class SelectionSorter
{
    // public enum comp {COMP_LESS,COMP_EQUAL,COMP_GRTR};
    private int min;
    // private int m=0;
    public void Sort(int[] list)
    {
        for (int i = 0; i < list.Length - 1; ++i)
        {
            min = i;
            for (int j = i + 1; j < list.Length; ++j)
            {
                if (list[j] < list[min])
                    min = j;
            }
            int t = list[min];
            list[min] = list[i];
            list[i] = t;
            // Console.WriteLine("{0}",list[i]);
        }
 
    }
}
public class MainClass
{
    public static void Main()
    {
        int[] iArrary = new int[] { 1, 5, 3, 6, 10, 55, 9, 2, 87, 12, 34, 75, 33, 47 };
        SelectionSorter ss = new SelectionSorter();
        ss.Sort(iArrary);
        for (int m = 0; m <= 13; m++)
            Console.WriteLine("{0}", iArrary[m]);
 
    }
}
  • 直接插入排序
    直接插入排序(straight insertion sort)的作法是:

每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序。

第一趟比較前兩個數,然後把第二個數按大小插入到有序表中; 第二趟把第三個數據與前兩個數從後向前掃描,把第三個數按大小插入到有序表中;依次進行下去,進行了(n-1)趟掃描以後就完成了整個排序過程。

直接插入排序屬於穩定的排序,時間複雜性爲o(n^2),空間複雜度爲O(1)。

直接插入排序是由兩層嵌套循環組成的。外層循環標識並決定待比較的數值。內層循環爲待比較數值確定其最終位置。直接插入排序是將待比較的數值與它的前一個數值進行比較,所以外層循環是從第二個數值開始的。當前一數值比待比較數值大的情況下繼續循環比較,直到找到比待比較數值小的並將待比較數值置入其後一位置,結束該次循環。

值得注意的是,我們必需用一個存儲空間來保存當前待比較的數值,因爲當一趟比較完成時,我們要將待比較數值置入比它小的數值的後一位 插入排序類似玩牌時整理手中紙牌的過程。插入排序的基本方法是:每步將一個待排序的記錄按其關鍵字的大小插到前面已經排序的序列中的適當位置,直到全部記錄插入完畢爲止。

 第一層循環是爲了依次將數組中的值放入到有序表裏,這裏循環是從1開始,第1個元素就是有序表。比如循環進到了5,那麼前五個元素就是有序表,後面的就是無序表
 第二層循環是爲了形成有序表,第一次外層循環,都有一個元素放到有序表中,並形成新的有序表。
C#代碼:(我寫的不是很簡練)
public class SortHelper
    {
        public static void InsertSort<T>(T[] array) where T : IComparable
        {
            int length = array.Length;
            for (int i = 1; i < length; i++)
            {
                T temp = array[i];
                if (temp.CompareTo(array[i - 1])<0)
                {
                    for (int j = 0; j < i; j++)
                    {
                        if (temp.CompareTo(array[j])<0)
                        {
                            temp = array[j];
                            array[j] = array[i];
                            array[i] = temp;
                        }
                    }
                }
            }
        }
    }

int[] array = new int[] {23,15,27,90,69,66,158,45,32,1};
            Console.WriteLine("before insert sort");
            foreach (int i in array)
            {
                Console.Write(i+"->");
            }
            Console.WriteLine();
            SortHelper.InsertSort<int>(array);
            Console.WriteLine("after insert sort");
            foreach (int i in array)
            {
                Console.Write(i + "->");
            }
            Console.WriteLine();
            Console.Read();
輸出
before insert sort
23->15->27->90->69->66->158->45->32->1->
after insert sort
1->15->23->27->32->45->66->69->90->158->

- 冒泡排序算法

冒泡排序 作爲一種漸進複雜度O(N^2)的排序方法, 實質上屬於一種形式“玄妙的”選擇排序,只是相比選擇排序多了一些無意義的交換。它比選擇排序更難於理解和解釋(就在於它多次無意義的交換的干擾,不信你講給樓下阿姨比較下),又不具有插入排序的“在線”優點,跟極易理解的基數排序更不能在思維難度上相比,就算同快排,歸併相比,也具有複雜度高得多的劣勢。那麼爲什麼冒泡排序會作爲經典,成爲初學者學習語言的遇到的首個算法?

int temp;
            int[] arrSort = new int[] { 10, 8, 3, 5, 6, 7, 9 };
            for (int i = 0; i < arrSort.Length; i++)
            {
                for (int j = i + 1; j < arrSort.Length; j++)
                {
                    if (arrSort[j] < arrSort[i])
                    {
                        temp = arrSort[j];
                        arrSort[j] = arrSort[i];
                        arrSort[i] = temp;
                    }
                }
}

冒泡排序並不是一種有工程意義的排序算法。不如說std::sort以外的也許都沒有工程意義……
但是講冒泡排序可以帶出幾個問題:
冒泡排序只有一個基本操作就是比較和交換相鄰的兩個元素,可以體現將一個複雜的問題轉化爲非常簡單的操作的疊加的思想
冒泡排序看上去每一次循環都差不多,爲什麼可以在有限次循環之內結束,如何分析算法的正確性,這是一個不錯的數學問題
分析複雜度簡單,一眼就看得出來是O(n^2)。代碼也的確是最短的。邊界條件很少。甚至於多循環幾遍問題也不大。
分析完冒泡排序會發現,其他O(n2)的算法的原理都跟冒泡排序有共通之處:用O(n)的時間排出一個元素。所以講其他O(n2)排序就不用花太多時間解釋了。
嘛其實哪個都不是什麼關鍵問題,其實也許最大的原因是因爲這麼講沒啥毛病,所以也沒必要改吧……對於初學者來說,寫一個冒泡排序寫對的概率其實要比其他O(n^2)的算法高不少。

另外冒泡排序有個小優點是很容易展開成沒有循環的形式,而且代碼也不會太難看,比如說我現在固定要排的是四個數,就可以寫:
#define cs(a,b) (if(a>b){int t; t = a; a = b; b = t;})
cs(a,b);cs(b,c);cs(c,d);cs(a,b);cs(b,c);cs(a,b);
用別的排序就不太可能了。雖然現在編譯器都可以自動循環展開了,但以前並不是這樣,而且這樣小清新的代碼多好啊

  • 快速排序
    算法思想簡單描述:
    快速排序是對冒泡排序的一種本質改進。它的基本思想是通過一趟
    掃描後,使得排序序列的長度能大幅度地減少。在冒泡排序中,一次
    掃描只能確保最大數值的數移到正確位置,而待排序序列的長度可能只
    減少1。快速排序通過一趟掃描,就能確保某個數(以它爲基準點吧)
    的左邊各數都比它小,右邊各數都比它大。然後又用同樣的方法處理
    它左右兩邊的數,直到基準點的左右只有一個元素爲止。它是由
    C.A.R.Hoare於1962年提出的。

顯然快速排序可以用遞歸實現,當然也可以用棧化解遞歸實現。下面的
函數是用遞歸實現的,有興趣的朋友可以改成非遞歸的。

快速排序是不穩定的。最理想情況算法時間複雜度O(nlog2n),最壞O(n2)

想到了快速排序,於是自己就用C#實現了快速排序的算法:
快速排序的基本思想:
分治法,即,分解,求解,組合 .

分解:
在 無序區R[low…high]中任選一個記錄作爲基準(通常選第一個記錄,並記爲keyValue,其下標爲keyValuePosition),以此爲基準劃分成兩個較小的 子區間R[low,keyValuePosition- 1]和R[keyValuePosition+ 1 , high],並使左邊子區間的所有記錄均小於等於基準記錄,右邊子區間的所有記錄均大於等於基準記錄,基準記錄無需參加後續的排序。而劃分的關鍵是要求出 基準記錄所在的位置keyValuePosition.

求解:
通過遞歸調用快速排序對左、右子區間R[low…keyValuePosition-1]和R[keyValuePosition+1…high]快速排序

組合:
當"求解"步驟中的兩個遞歸調用結束時,其左、右兩個子區間已有序。對快速排序而言,"組合"步驟無須做什麼,可看作是空操作。

具體過程:
設序列爲R[low,high],從其中選第一個爲基準,設爲keyValue,然後設兩個指針i和j,分別指向序列R[low,high]的起始和結束位置上:
1),將i逐漸增大,直到找到大於keyValue的關鍵字爲止;
2),將j逐漸減少,直到找到小於等於keyValue的關鍵字爲止;
3),如果i<j,即R[i,j]的元素數大於1,則交換R[i]和R[j];
4),將基準記錄keyValue放到合適的位置上,即i和j同時指向的位置(或者同時指向的位置-1),則此位置爲新的keyValuePosition。

備註:
快速排序是不穩定排序,即相同的關鍵字排序後,相對位置是不確定的。
下面是我的C#實現的代碼

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace QuickSort
{
    class QuickSort
    {
        static void Main(string[] args)
        {
            //聲明數據進行相應的測試
            int[] myArray = new int[]{45, 36, 18, 53, 72, 30, 48, 93, 15, 36};
            //myArray = new int[] { 3, 4, 5, 1};
            //myArray = new int[] { 3, 4, 2, 1};
 
            int lowIndex = 0;                                       //數組的起始位置(從0開始)
            int highIndex = myArray.Length - 1;         //數組的終止位置
 
            //快速排序
            QuickSortFunction(myArray, lowIndex, highIndex);
 
            //輸出排完之後的數組
            for (int i = 0; i < myArray.Length; i++)
            {
                Console.WriteLine(myArray[i].ToString());
            }
        }
 
        //快速排序(目標數組,數組的起始位置,數組的終止位置)
        private static void QuickSortFunction(int[] array, int low, int high)
        {
            try
            {
                int keyValuePosition;   //記錄關鍵值的下標
 
                //當傳遞的目標數組含有兩個以上的元素時,進行遞歸調用。(即:當傳遞的目標數組只含有一個元素時,此趟排序結束)
                if (low < high) 
                {
                    keyValuePosition = keyValuePositionFunction(array, low, high);  //獲取關鍵值的下標(快排的核心)
 
                    QuickSortFunction(array, low, keyValuePosition - 1);    //遞歸調用,快排劃分出來的左區間
                    QuickSortFunction(array, keyValuePosition + 1, high);   //遞歸調用,快排劃分出來的右區間
                }
            }
            catch (Exception ex)
            { }
        }
 
        //快速排序的核心部分:確定關鍵值在數組中的位置,以此將數組劃分成左右兩區間,關鍵值遊離在外。(返回關鍵值應在數組中的下標)
        private static int keyValuePositionFunction(int[] array, int low, int high)
        {
            int leftIndex = low;        //記錄目標數組的起始位置(後續動態的左側下標)
            int rightIndex = high;      //記錄目標數組的結束位置(後續動態的右側下標)
 
            int keyValue = array[low];  //數組的第一個元素作爲關鍵值
            int temp;
 
            //當 (左側動態下標 == 右側動態下標) 時跳出循環
            while (leftIndex < rightIndex)
            {
                while (leftIndex < rightIndex && array[leftIndex] <= keyValue)  //左側動態下標逐漸增加,直至找到大於keyValue的下標
                {
                    leftIndex++;
                }
                while (leftIndex < rightIndex && array[rightIndex] > keyValue)  //右側動態下標逐漸減小,直至找到小於或等於keyValue的下標
                {
                    rightIndex--;
                }
                if(leftIndex < rightIndex)  //如果leftIndex < rightIndex,則交換左右動態下標所指定的值;當leftIndex==rightIndex時,跳出整個循環
                {
                    temp = array[leftIndex];
                    array[leftIndex] = array[rightIndex];
                    array[rightIndex] = temp;
                }
            }
 
            //當左右兩個動態下標相等時(即:左右下標指向同一個位置),此時便可以確定keyValue的準確位置
            temp = keyValue;
            if (temp < array[rightIndex])   //當keyValue < 左右下標同時指向的值,將keyValue與rightIndex - 1指向的值交換,並返回rightIndex - 1
            {
                array[low] = array[rightIndex - 1];
                array[rightIndex - 1] = temp;
                return rightIndex - 1;
            }
            else //當keyValue >= 左右下標同時指向的值,將keyValue與rightIndex指向的值交換,並返回rightIndex
            {
                array[low] = array[rightIndex];
                array[rightIndex] = temp;
                return rightIndex;
            }
        }
    }
}

- 堆排序

功能:堆排序
輸入:數組名稱(也就是數組首地址)、數組中元素個數
算法思想簡單描述:
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
堆的定義如下:具有n個元素的序列(h1,h2,…,hn),當且僅當
滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,…,n/2)
時稱之爲堆。在這裏只討論滿足前者條件的堆。

由堆的定義可以看出,堆頂元素(即第一個元素)必爲最大項。完全二叉樹可以
很直觀地表示堆的結構。堆頂爲根,其它爲左子樹、右子樹。
初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲順序,
使之成爲一個堆,這時堆的根節點的數最大。然後將根節點與堆的最後一個節點
交換。然後對前面(n-1)個數重新調整使之成爲堆。依此類推,直到只有兩個節點
的堆,並對它們作交換,最後得到有n個節點的有序序列。

從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素
交換位置。所以堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數
實現排序的函數。

堆排序是不穩定的。算法時間複雜度O(nlog2n)。

//堆排序算法(傳遞待排數組名,即:數組的地址。故形參數組的各種操作反應到實參數組上)
     private static void HeapSortFunction(int[] array)
        {
            try
            {
                BuildMaxHeap(array);    //創建大頂推(初始狀態看做:整體無序)
         for (int i = array.Length -1; i >0; i--)
                {
                    Swap(ref array[0], ref array[i]); //將堆頂元素依次與無序區的最後一位交換(使堆頂元素進入有序區)
                    MaxHeapify(array, 0, i); //重新將無序區調整爲大頂堆
                }
            }
            catch (Exception ex)
            { }
        }
 
        ///<summary>
        /// 創建大頂推(根節點大於左右子節點)
        ///</summary>
        ///<param name="array">待排數組</param>
     private static void BuildMaxHeap(int[] array)
        {
            try
            {
                //根據大頂堆的性質可知:數組的前半段的元素爲根節點,其餘元素都爲葉節點
         for (int i = array.Length /2-1; i >=0; i--) //從最底層的最後一個根節點開始進行大頂推的調整
                {
                    MaxHeapify(array, i, array.Length); //調整大頂堆
                }
            }
            catch (Exception ex)
            { }
        }
 
        ///<summary>
        /// 大頂推的調整過程
        ///</summary>
        ///<param name="array">待調整的數組</param>
        ///<param name="currentIndex">待調整元素在數組中的位置(即:根節點)</param>
        ///<param name="heapSize">堆中所有元素的個數</param>
     private static void MaxHeapify(int[] array, int currentIndex, int heapSize)
        {
            try
            {
                int left =2* currentIndex +1;    //左子節點在數組中的位置
         int right =2* currentIndex +2;   //右子節點在數組中的位置
         int large = currentIndex;   //記錄此根節點、左子節點、右子節點 三者中最大值的位置
 
                if (left < heapSize && array[left] > array[large])  //與左子節點進行比較
                {
                    large = left;
                }
                if (right < heapSize && array[right] > array[large])    //與右子節點進行比較
                {
                    large = right;
                }
                if (currentIndex != large)  //如果 currentIndex != large 則表明 large 發生變化(即:左右子節點中有大於根節點的情況)
                {
                    Swap(ref array[currentIndex], ref array[large]);    //將左右節點中的大者與根節點進行交換(即:實現局部大頂堆)
                    MaxHeapify(array, large, heapSize); //以上次調整動作的large位置(爲此次調整的根節點位置),進行遞歸調整
                }
            }
            catch (Exception ex)
            { }
        }
 
        ///<summary>
        /// 交換函數
        ///</summary>
        ///<param name="a">元素a</param>
        ///<param name="b">元素b</param>
     private static void Swap(refint a, refint b)
        {
            int temp =0;
            temp = a;
            a = b;
            b = temp;
        }

- 歸併排序

歸併排序(Merge Sort)是利用"歸併"技術來進行排序。歸併是指將若干個已排序的子文件合併成一個有序的文件。歸併排序有兩種方式:1): 自底向上的方法 2):自頂向下的方法

1、 自底向上的方法
(1) 自底向上的基本思想
自底向上的基本思想是:第1趟歸併排序時,將待排序的文件R[1…n]看作是n個長度爲1的有序子文件,將這些子文件兩兩歸併,若n爲偶數,則得到n/2個長度爲2的有序子文件;若n爲奇數,則最後一個子文件輪空(不參與歸併)。故本趟歸併完成後,前n/2 - 1個有序子文件長度爲2,但最後一個子文件長度仍爲1;第2趟歸併則是將第1趟歸併所得到的n/2個有序的子文件兩兩歸併,如此反覆,直到最後得到一個長度爲n的有序文件爲止。
上述的每次歸併操作,均是將兩個有序的子文件合併成一個有序的子文件,故稱其爲"二路歸併排序"。類似地有k(k>2)路歸併排序。

2、自頂向下的方法(本文主要介紹此種方法,下面的文字都是對此種方法的解讀)

(1) 自頂向下的基本思想
採用分治法進行自頂向下的算法設計,形式更爲簡潔。
自頂向下的歸併排序:是利用遞歸和分而治之的技術將數據序列劃分成爲越來越小的半子表,再對半子表排序,最後再用遞歸步驟將排好序的半子表合併成爲越來越大的有序序列,歸併排序包括兩個步驟,分別爲:

  1)劃分子表

  2)合併半子表

(1)分治法的三個步驟
設歸併排序的當前區間是R[low…high],分治法的三個步驟是:
①分解:將當前區間一分爲二,即求分裂點
②求解:遞歸地對兩個子區間R[low…mid]和R[mid+1…high]進行歸併排序;
③組合:將已排序的兩個子區間R[low…mid]和R[mid+1…high]歸併爲一個有序的區間R[low…high]。
遞歸的終結條件:子區間長度爲1(一個記錄自然有序)。

如下演示遞歸的整個過程:

遞歸便是深度遍歷(如下由左至右進行遍歷):假設有這樣的一列數組{9,8,7,6,5,4,3,2,1}進行劃分的順序如下:

{9,8,7,6,5,4,3,2,1} --> {9,8,7,6,5},{4,3,2,1}

{9,8,7,6,5} --> {9,8,7},{6,5}

                    {9,8,7} --> {9,8},{7}

                                      {9,8} --> {9},{8}

                    {6,5} -->{6},{5}

{4,3,2,1} --> {4,3},{2,1}

                  {4,3} -->{4},{3}

                  {2,1} -->{2},{1}

當深度劃分到左右數組都只剩1個元素的時候,進行上述逆序的合併:

{9},{8} --> {8,9} 然後和 {7} --> {7,8,9}

                            {6},{5} --> {5,6}    然後 {7,8,9}和{5,6} --> {5,6,7,8,9}

                                 {2},{1} --> {1,2}

                                 {4},{3} --> {3,4}   然後 {1,2}和 {3,4} --> {1,2,3,4}

                                                                                                                     最終{5,6,7,8,9}和{1,2,3,4} --> {1,2,3,4,5,6,7,8,9}

具體實現代碼如下所示:

//歸併排序(目標數組,子表的起始位置,子表的終止位置)
        private static void MergeSortFunction(int[] array, int first, int last)
        {
            try
            {
                if (first < last)   //子表的長度大於1,則進入下面的遞歸處理
                {
                    int mid = (first + last) / 2;   //子表劃分的位置
                    MergeSortFunction(array, first, mid);   //對劃分出來的左側子表進行遞歸劃分
                    MergeSortFunction(array, mid + 1, last);    //對劃分出來的右側子表進行遞歸劃分
                    MergeSortCore(array, first, mid, last); //對左右子表進行有序的整合(歸併排序的核心部分)
                }
            }
            catch (Exception ex)
            { }
        }
 
        //歸併排序的核心部分:將兩個有序的左右子表(以mid區分),合併成一個有序的表
        private static void MergeSortCore(int[] array, int first, int mid, int last)
        {
            try
            {
                int indexA = first; //左側子表的起始位置
                int indexB = mid + 1;   //右側子表的起始位置
                int[] temp = new int[last + 1]; //聲明數組(暫存左右子表的所有有序數列):長度等於左右子表的長度之和。
                int tempIndex = 0;
                while (indexA <= mid && indexB <= last) //進行左右子表的遍歷,如果其中有一個子表遍歷完,則跳出循環
                {
                    if (array[indexA] <= array[indexB]) //此時左子表的數 <= 右子表的數
                    {
                        temp[tempIndex++] = array[indexA++];    //將左子表的數放入暫存數組中,遍歷左子表下標++
                    }
                    else//此時左子表的數 > 右子表的數
                    {
                        temp[tempIndex++] = array[indexB++];    //將右子表的數放入暫存數組中,遍歷右子表下標++
                    }
                }
                //有一側子表遍歷完後,跳出循環,將另外一側子表剩下的數一次放入暫存數組中(有序)
                while (indexA <= mid)
                {
                    temp[tempIndex++] = array[indexA++];
                }
                while (indexB <= last)
                {
                    temp[tempIndex++] = array[indexB++];
                }
 
                //將暫存數組中有序的數列寫入目標數組的制定位置,使進行歸併的數組段有序
                tempIndex = 0;
                for (int i = first; i <= last; i++)
                {
                    array[i] = temp[tempIndex++];
                }
            }
            catch (Exception ex)
            { }
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章