六種經常使用排序算法的實現及其優化

2018-12-10-17:22:29

1.排序

  定義 : 排序是計算機內常常進行的一種操做,其目的是將一組「無序」的記錄序列調整爲「有序」的記錄序列。份內部排序和外部排序,若整個排序過程不須要訪問外存便能完成,則稱此類排序問題爲內部排序。反之,若參加排序的記錄數量很大,整個序列的排序過程不可能在內存中完成,則稱此類排序問題爲外部排序。內部排序的過程是一個逐步擴大記錄的有序序列長度的過程。c++

  分類 :  算法

    穩定排序:假設在待排序的文件中,存在兩個或兩個以上的記錄具備相同的關鍵字,在
  用某種排序法排序後,若這些相同關鍵字的元素的相對次序仍然不變,則這種排序方法
  是穩定的。其中冒泡,插入,基數,歸併屬於穩定排序,選擇,快速,希爾,堆屬於不穩定排序。
    
    就地排序:若排序算法所需的輔助空間並不依賴於問題的規模n,即輔助空間爲O(1),
  則稱爲就地排序。

2.具體排序算法描述

  ① 冒泡排序:

      算法描述: 數組

        將數組分爲無序和有序兩部分,初始時無序數列的邊界下標爲總數組的長度減去一的位置,從無序數列的第一個數開始作交換,若是發現這個數比下一個數大則交換這兩個數,直至無序數列的最後一個數爲一輪交換,這輪交換使得無序數列的最大元素放到了它邊界值的下一個位置,每完成一輪交換都更新一次無序數列的邊界下標,直至整個數組都變爲有序則完成整個排序。ide

      優劣: 
 
        缺點一:當數組已經有序時不能及時推出,而是依然判斷是否交換。
        缺點兒:當無序數列尾部存在部分有序時任然對有序部分進行判斷是否交換。
 
      C++ 代碼:
1 void Babble_Sort1(ElementType *array, int length) { 2     for(int i = 0; i < length - 1; i ++) 3         for(int j = 0; j < length - 1 - i; j ++) 4             if(array[j] > array[j + 1]) 5                 swap(array[j], array[j + 1]); 6 }
 
      優化思路:當數組已有序結束排序,無序部分尾部有序時改變無序數列的邊界值。
 
      優化後c++代碼:
 
 1 void Babble_Sort2(ElementType *array, int length) {  2     bool IsSorted;  3     int LastPosition;//記錄每回合最後一次交換位置的元素的下標
 4     int SortBorder = length - 1;//記錄無序數列的邊界
 5     for(int i = 0; i < length - 1; i ++) {  6         IsSorted = true;  7         for(int j = 0; j < SortBorder; j ++)  8             if(array[j] < array[j + 1]) {  9                 swap(array[j], array[j + 1]); 10                 IsSorted = false; 11                 LastPosition = j; 12  } 13         SortBorder = LastPosition; 14         if(IsSorted) 15             break; 16  } 17 }

 

 ② 直接插入排序:

      算法描述: 性能

         1.將原數組分爲未排序和已排序兩部分。優化

         2.從第一個元素開始,該元素能夠認爲已經被排序;ui

         3.取出下一個元素,在已經排序的元素序列中從後向前掃描;spa

         4.若是該元素(已排序)大於新元素(待插入),將該元素移到下一位置;code

         5.重複步驟4,直到找到已排序的元素小於或者等於新元素的位置;blog

         6.將新元素插入到該位置後;

         7.重複步驟3~6。

 

      算法分析: 

         插入排序在實現上,一般採用in-place排序(即只需用到O(1)的額外空間的排序),於是在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。
 
      C++ 代碼:
 1 void Insertion_Sort(ElementType *array, int length) {  2     int i, j;  3  ElementType Tmp;  4     for(i = 1; i < length; i ++) {  5         Tmp = array[i];  6         for(j = i; j > 0 && array[j-1] > Tmp; j --)  7             array[j] = array[j-1];  8         array[j] = Tmp;  9  } 10 }

 

 ③ 選擇排序:

      算法描述: 

         將數組分爲無序和有序兩個部分,起初視數組都爲無序,每次讓數組無序部分的第一個元素做爲擂主,讓其後的元素開始打擂,每次打至數組的最後一個元素,若是擂主變了(後續數組中存在比擂主小的元素)

      ,則將擂主歸併做爲有序部分的最後一個元素並按照上述規則繼續打擂,直至數組的最後一個元素。

 

      優劣:
          選擇排序與其它交換排序相比減小了數組元素的交換次數,從而使得選擇排序的平均效率通常比其餘交換排序高。
     
      C++ 代碼:
 1 void Seletion_Sort(ElementType *array, int length) {  2     int index;  3     for(int i = 0; i < length - 1; i ++) {  4         index = i;  5         for(int j = i + 1; j < length; j ++)  6             if(array[j] > array[index])  7                 index = j;  8         if(index != i)  9  swap(array[index], array[i]); 10  } 11 }

       

      改進思路:

         每次打擂只能找出無序數組元素中最大(或最小)的元素,能夠考慮每次找出最大和最小的元素,減小循環的次數,從而提升查找的效率。

      

      二元選擇排序

 

          c++代碼:

 1 void Double_Seletion_Sort(ElementType *array, int length) {  2     int MinPos, MaxPos;//這裏咱們將i看作是擂主,j看做是打擂者
 3     for(int i = 0; i < length / 2; i ++) {  4         MinPos = MaxPos = i;//讓擂主同時與最大最小值打比賽
 5         for(int j = i + 1; j < length - i; j ++) {  6             if(array[j] > array[MaxPos]) {  7                 MaxPos = j;  8                 continue;//若是發現array[j] > array[MaxPos]則其必定不會小於array[MinPos]
 9  } 10             if(array[j] < array[MinPos]) 11                 MinPos = j; 12  } 13         if(MinPos != i)//將新星擂主歸入有序序列的最後一位,若是擂主沒變則不用歸入
14  swap(array[i], array[MinPos]); 15         if(MaxPos == i)//若是擂主位置爲最大值,則剛剛交換最小值時已經將最大值換到了最小值打雷成功的打擂者身上即(MinPos)
16             MaxPos = MinPos; 17         if(MaxPos != length - 1 - i)//若是擂主不是無序部分最後一位則將其與最後一位交換,歸入有序序列
18             swap(array[length - 1 - i], array[MaxPos]); 19  } 20 }

 

       

 ④ 歸併排序:

      定義:

        歸併排序(MERGE-SORT)是創建在歸併操做上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。將已有序的子序列合併,獲得徹底有序的序列;即先使每一個

      子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。

      算法描述:  

        1.把長度爲n的輸入序列分紅兩個長度爲n/2的子序列;

        2.對這兩個子序列分別採用歸併排序;

        3.將兩個排序好的子序列合併成一個最終的排序序列。

 

      優劣:
        歸併排序是一種穩定的排序方法。和選擇排序同樣,歸併排序的性能不受輸入數據的影響,但表現比選擇排序好的多,由於始終都是O(nlogn)的時間複雜度。
        代價是須要額外的內存空間。 
 
      C++ 代碼:
 1 void MergeSort(ElementType *array, ElementType *TmpArray, int Left, int Right) {  2     int Center;  3     if(Left < Right) {  4         Center = Left + (Right - Left) / 2; //避免數據類型溢出
 5  MergeSort(array, TmpArray, Left, Center);  6         MergeSort(array, TmpArray, Center + 1, Right);  7         Merge(array, TmpArray, Left, Center + 1, Right);  8  }  9 } 10 
11 void Merge(ElementType *array, ElementType *TmpArray, int LPos, int RPos, int RightEnd) { 12     int LeftEnd = RPos - 1, TmpPos = LPos, NumElements = RightEnd - LPos + 1; 13     while(LPos <= LeftEnd && RPos <= RightEnd) { 14         if(array[LPos] <= array[RPos]) 15             TmpArray[TmpPos ++] = array[LPos ++]; 16         else
17             TmpArray[TmpPos ++] = array[RPos ++]; 18  } 19     while(LPos <= LeftEnd) 20         TmpArray[TmpPos ++] = array[LPos ++]; 21     while(RPos <= RightEnd) 22         TmpArray[TmpPos ++] = array[RPos ++]; 23     for(int i = 0; i < NumElements; i ++, RightEnd --) //Copy TmpArray back
24         array[RightEnd] = TmpArray[RightEnd]; 25 }

 

 

 ⑤ 快速排序:

      基本思想: 

        經過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的全部數據都比另一部分的全部數據都要小,而後再按此方法對這兩部分數據分別進行快速排序,整個排序過程能夠遞歸進行,以此達到

      整個數據變成有序序列。

      

      算法描述:

        1.若是需排序元素大於指定數目則執行快速排序,不然執行其它簡單排序。(通常元素多於10個時進入快速排序,事實證實,當元素個數過少時使用快速排序會比其它程序慢的多,而且這裏咱們選擇三值取

      中法,若是元素個數過少會產生未預知的錯誤)。

        2.利用三值取中法獲取一個樞紐元(於此同時將樞紐元放在待排序序列的最後一位)。

        3.將數組分爲三個不相交的集合,即x < pivot , pivot , x > pivot 。

        4.對x < pivot執行上述1,2,3操做, pivot, 對 x > pivot執行上述1,2,3操做。

      優劣: 
 
        快速排序擅長對大量數據進行排序。
 
      C++ 代碼:
ElementType Median3(ElementType *array, int Left, int Right) { int Center = Left + (Right - Left) / 2;//避免數據類型溢出
    if(array[Left] > array[Center]) swap(array[Left], array[Center]); if(array[Left] > array[Right]) swap(array[Left], array[Right]); if(array[Center] > array[Right]) swap(array[Center], array[Right]); // Invariant : array[Left] <= array[Center] <= array[Right]
    swap(array[Center], array[Right - 1]);//Hide pivot
    return array[Right - 1];// Return pivot
} #define CutoffRange (10)
void QuicklySort(ElementType *array, int Left, int Right) { ElementType pivot; if(Left + CutoffRange <= Right) { //當待排序元素個數多於CutoffRange時採用快速排序
        pivot = Median3(array, Left, Right);//選取樞紐元 //注意下方對i和j的操做爲++ i, ++ j操做,即跳過了第一個和最後一個元素,這是由於在進行三數取中法的時候待排序的第一個和最後一個數已經在它正確的位置上了
        int i = Left, j = Right - 1; while(true) {//將數組中小於pivot的元素放到左邊,大於pivot的元素放到右邊
            while(array[++ i] < pivot); while(array[-- j] > pivot); if(i < j)//當同時找到不知足上述條件的兩個值時,將其交換就是最好的選擇
 swap(array[i], array[j]); else
                break; } swap(array[i], array[Right - 1]);//最後將樞紐元放到他在數組中的正確位置
        QuicklySort(array, Left, i - 1); QuicklySort(array, i + 1, Right); } else Double_Seletion_Sort(array + Left, Right - Left + 1); }

 

 ⑥希爾排序:

      1959年Shell發明,第一個突破O(n2)的排序算法,是簡單插入排序的改進版。它與插入排序的不一樣之處在於,它會優先比較距離較遠的元素。希爾排序又叫縮小增量排序。

 

      算法思路:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序。

 

      算法描述: 

         1.選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;

         2.按增量序列個數k,對序列進行k 趟排序;

         3.每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列做爲一個表來處理,表長度即爲整個序列的長度。

 

      C++ 代碼:

 

 1 void ShellSort(ElementType *array, Length length) {  2     int i, j, Increment = 1;  3  ElementType Tmp;  4     while(Increment < length / 3) Increment = 3 * Increment + 1;//找到起初最大的間隔序列之首
 5     while(Increment >= 1) {  6         for(i = Increment; i < length; i ++) {  7             Tmp = array[i];//用來保存待插入元素
 8             for(j = i;  9                     j >= Increment && array[j - Increment] > Tmp; 10                     j -= Increment)//將原數組以Increment爲間隔分開,而後對每部分分別進行插入排序
11                 array[j] = array[j - Increment]; 12             array[j] = Tmp; 13  } 14         Increment /= 3; 15  } 16 }

 

 
      算法分析:
         希爾排序是按照不一樣步長對元素進行插入排序,當剛開始元素很無序的時候,步長最大,因此插入排序的元素個數不多,速度很快;當元素基本有序了,步長很小,插入排序對於有序的
      序列效率很高。因此,希爾排序的時間複雜度會比o(n^2)好一些。
      
      縮小增量的選取:
        起初,希爾的論文裏只是簡單的將初始增量規定爲length/2,而後每次將增量縮小到原來的一半,當增量爲1時即執行普通的插入排序,可是後來有人證實取半法並非最優的,Knuth提出以Increment = 3 * b + 1的增量
      序列效率最爲突出(Increment初值爲1)。
相關文章
相關標籤/搜索