8種排序演算法

iamdll發表於2019-03-21

首先來看看排序演算法有哪八種:

          

 
1.直接插入排序

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

         穩定的排序,最壞時間複雜性為O(n^2),空間複雜度為O(1)。

           public void InsertSort<T>(T[] arry,Comparison<T> comparison)
           {
                //直接插入排序是將待比較的數值與它的前一個數值進行比較,所以外層迴圈是從第二個數值開始的
                for (int i = 1; i < arry.Length; i++)
                {
                    T temp = arry[i];
                    int j = i - 1;
     
                    // 在已排好序的數列段中找到比新數值小的數值
                    while (j >= 0 && comparison(arry[j] , temp) == 1)
                    {
                        //將比新數值大的數值向後移
                        arry[j + 1] = arry[j];
                        j--;
                    }
                    // 如果在已排好序的數列段中找到了比新數值小的數值
                    // 將數值替換到此位置
                    arry[j + 1] = temp;
                }
            }

 
2.希爾排序(有點疑惑網上的希爾排序都只用三個迴圈,博主是沒有理解透徹,按照原理 應該至少有四個迴圈,因為插入排序就有兩個迴圈,希望各位看官有知道的能夠為博主解惑)

原理 : 將待排序陣列按照步長gap進行分組,然後將每組的元素利用直接插入排序的方法進行排序;每次將gap折半減小,迴圈上述操作;當gap=1時,利用直接插入,完成排序。

        是非穩定排序演算法

 public void ShellSort<T>(T[] arry,Comparison<T> comparison)
       {
            int Length = arry.Length;
            for (int gap = Length / 2; gap > 0; gap = gap / 2) //分組,得到組數
            {
                for (int i = gap; i < gap * 2; i++)//對每一組進行插入排序
                {
                    for (int j = i; j < Length; j += gap)
                    {
                        T temp = arry[j];
                        int k = j - gap;
                        while (k >= 0 && comparison(temp , arry[k]) == -1)
                        {
                            arry[k + gap] = arry[k];
                            k = k - gap;
                        }
                        arry[k + gap] = temp;
 
                    }
                }
            }
        }
---------------------

 

 
3.簡單選擇排序

原理 : 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。

        進行移動操作的時間複雜度為O(n)。進行比較操作的時間複雜度為O(n^2)

        public void SimpleSelectSort<T>( T[] arry , Comparison<T> comparison)
        {
                int k = 0;
                for (int i=0;i<arry.Length;i++)
                {
                    k = i;
                    for (int j = i + 1;j<arry.Length;j++)
                    {
                        if(comparison(arry[k] , arry[j]) == 1)
                        {
                            k = j;
                        }
                    }
                    Exchange(ref arry[i],ref arry[k]);
                }
            }

 
4.堆排序

原理 : 是利用一種被稱作二叉堆的資料結構進行排序的排序演算法。

          二叉堆中,有兩個與所維護陣列相關的屬性。Length表示陣列的元素個數,而HeapSize則表示二叉堆中所維護的陣列中的元素的個數(並不是陣列中的所有元素都一定是二叉堆的有效元素)。因此,根據上述定義有: 0 <= HeapSize <= Length。

         二叉堆可分為最大堆和最小堆兩種型別。在最大堆中,二叉樹上所有的節點都不大於其父節點,即 A[Parent(i)] >= A[i]。最小堆正好相反:A[Parent(i)] <= A[i]。

         因為在呼叫MaxHeapify(MinHeapify)方法使根節點為A[i]的二叉堆滿足最大(小)堆性質時我們有其左右子堆均已滿足最大(小)堆性質這個假設,所以如果我們在將一個待排序的陣列構造成最大(小)堆時,需要自底向上地呼叫 MaxHeapify(MinHeapify)方法。

       在利用最大堆進行排序時,我們先將待排序陣列構造成一個最大堆,此時A[0](根節點)則為陣列中的最大元素,將A[0]與A[n - 1]交換,則把A[0]放到了最終正確的排序位置。然後通過將HeapSize減去1,將(交換後的)最後一個元素從堆中去掉。然後通過MaxHeapify方法將餘下的堆改造成最大堆,然後重複以上的交換。重複這一動作,直到堆中元素只有2個。則最終得到的陣列為按照升序排列的陣列。

            public void HeapSort<T>(T[] array, Comparison<T> comparison)
            {
                BuildMHeap<T>(array, comparison);
                for (int i = array.Length - 1; i > 0; i--)
                {
                    Exchange(ref array[i], ref array[0]);
                    MHeapify<T>(array, 0, i, comparison);
                }
            }
            //計算節點的父節點和子節點
            private int Parrent(int i)
            {
                return (i - 1) / 2;
            }
            private int Left(int i)
            {
                return 2 * i + 1;
            }
            private int Right(int i)
            {
                return 2 * i + 2;
            }
            //構建最大堆/最小堆
            private void BuildMHeap<T>(T[] array, Comparison<T> comparison)
            {
                for (int i = array.Length / 2 - 1; i >= 0; i--)
                {
                    MHeapify<T>(array, i, array.Length, comparison);
                    
                }
            }
            private void MHeapify<T>(T[] array, int i, int heapSize, Comparison<T> comparison)
            {
                int left = Left(i);
                int right = Right(i);
     
                int extremumIndex = i;
                if (left < heapSize && comparison(array[left], array[i]) > 0)
                {
                    extremumIndex = left;
                }
     
                if (right < heapSize && comparison(array[right], array[extremumIndex]) > 0)
                {
                    extremumIndex = right;
                }
     
                if (extremumIndex != i)
                {
                    Exchange<T>(ref array[extremumIndex], ref array[i]);
                    MHeapify<T>(array, extremumIndex, heapSize, comparison);
                }
            }

 
5.氣泡排序

原理 : 首先將一個記錄的關鍵字和第二個關鍵字進行比較,若為逆序,則將兩個記錄交換,然後比較第2個記錄和第3個記錄的關鍵字。依次類推,直至第N-1個記錄和第n個記錄的關鍵字進行過比較為止。上述過程稱為第一趟氣泡排序,執行n-1次上述過程後,排序完成。

優缺點 :

      優點:穩定 時間複雜度:理想情況下(陣列本來就是有序的),此時最好的時間複雜度為o(n),最壞的時間複雜度(資料反序的),此時的時間複雜度為o(n*n) 。  氣泡排序的平均時間負責度為o(n*n).  

       缺點:慢,每次只移動相鄰的兩個元素。

           public void BubbleSort<T>( T[] array,Comparison<T> comparison)
            {
                for (int i = 0; i < array.Length; i++)
                {
                    for (int j = 0; j < array.Length - 1 - i; j++)
                    {
                        //比較相鄰的兩個元素,如果前面的比後面的大,則交換位置
                        if (comparison(array[j] , array[j + 1]) == 1)
                        {
                            Exchange(ref array[j],ref array[j + 1]);
                        }
                    }
                }
            }

6.快速排序

     原理 : 快速排序法是採用遞迴的方式對待排序的數列進行若干次的操作,每次操作使得被操作的數列部分以某個元素為分界值分成兩部分,一部分小於該分界值,另一部分大於該分界值.該分界值一般被稱為"樞軸". 一般先以左邊第一個數作為分界值,將數列按該分界值分成左右兩部分,左邊部分小於該分界值,右邊部分大於該分界值,然後再對左右兩部分做重複的操作,直到最後完成排序。

      快速排序是一種不穩定的排序演算法

        public void QuickSort<T>(T[] array, Comparison<T> comparison, int left, int right)
        {
                //左邊小於右邊說明排序還沒有完成
                if (left < right)
                {
                    T middle = array[(left + right) / 2];
                    //注意初始化
                    int j = right + 1;
                    int i = left - 1;
                    while (true)
                    {
                        while (comparison(middle, array[++i]) > 0 && i < right) ;//左邊,先加的原因是防止找到的最左邊會超出界限
                        while (comparison(middle, array[--j]) < 0 && j > 0) ; //右邊
                        if (i >= j)
                            break;
                        Exchange<T>(ref array[i], ref array[j]);
                    }
                    for (int m = 0; m < array.Length; m++)
                    {
                        Console.Write(array[m] + " ");
                    }
                    Console.ReadLine();
                    QuickSort<T>(array, comparison, left, i - 1);
                    QuickSort<T>(array, comparison, j + 1, right);
                }
            }

7.歸併排序

         是分治法(Divide and Conquer)的一個非常典型的應用

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

      public void MergeSortFunction<T>(T[] array, Comparison<T> comparison, int first, int last)
            {
                try
                {
                    if (first < last)   //子表的長度大於1,則進入下面的遞迴處理
                    {
                        int mid = (first + last) / 2;   //子表劃分的位置
                        MergeSortFunction(array,comparison, first, mid);   //對劃分出來的左側子表進行遞迴劃分
                        MergeSortFunction(array, comparison, mid + 1, last);    //對劃分出來的右側子表進行遞迴劃分
                        MergeSortCore(array, comparison, first, mid, last); //對左右子表進行有序的整合(歸併排序的核心部分)
                    }
                }
                catch (Exception ex)
                { }
            }
     
            //歸併排序的核心部分:將兩個有序的左右子表(以mid區分),合併成一個有序的表
            private void MergeSortCore<T>(T[] array, Comparison<T> comparison, int first, int mid, int last)
            {
                try
                {
                    int indexA = first; //左側子表的起始位置
                    int indexB = mid + 1;   //右側子表的起始位置
                    T[] temp = new T[last + 1]; //宣告陣列(暫存左右子表的所有有序數列):長度等於左右子表的長度之和。
                    int tempIndex = 0;
                    while (indexA <= mid && indexB <= last) //進行左右子表的遍歷,如果其中有一個子表遍歷完,則跳出迴圈
                    {
                        if (comparison(array[indexA] , array[indexB]) <= 0) //此時左子表的數 <= 右子表的數
                        {
                            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)
                { }
            }

8.基數排序

        屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,演算法的時間複雜度是O(n)

 

     public static void RadixSort( int[] array, int array_x = 10, int array_y = 100)
            {
                /* 最大數字不超過999999999...(array_x個9) */
                for (int i = 0; i < array_x; i++)
                {
                    int[,] bucket = new int[array_x, array_y];
                    foreach (var item in array)
                    {
                        int temp = (item / (int)Math.Pow(10, i)) % 10;
                        for (int l = 0; l < array_y; l++)
                        {
                            if (bucket[temp, l] == 0)
                            {
                                bucket[temp, l] = item;
                                break;
                            }
                        }
                    }
                    for (int o = 0, x = 0; x < array_x; x++)
                    {
                        for (int y = 0; y < array_y; y++)
                        {
                            if (bucket[x, y] == 0) continue;
                            array[o++] = bucket[x, y];
                        }
                    }
                }
            }

輔助函式:

           #region 交換值
            public void Exchange<T>(ref T x, ref T y)
            {
                T temp = x;
                x = y;
                y = temp;
            }
            #endregion
            #region 比較兩個int的值
            public int ComparisonInt(int x,int y)
            {
                if(x > y)
                {
                    return 1;
                }
                else if(x == y)
                {
                    return 0;
                }
                else
                {
                    return -1;
                }
            }
            #endregion

原始碼地址:

           https://github.com/webloverand/Interview
參考列表:

          1.資料結構常見的八大排序演算法(詳細整理): https://www.cnblogs.com/hokky/p/8529042.html

          2.堆排序——C#實現 : https://blog.csdn.net/zhuo_wp/article/details/78251777

          3.歸併排序演算法(C#實現): https://www.cnblogs.com/mingmingruyuedlut/archive/2011/08/18/2144984.html
 

相關文章