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


2018-12-10-17:22:29

1.排序

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

  分類 :  

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

2.具體排序算法描述

  ① 冒泡排序:

      算法描述: 

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

      優劣: 
 
        缺點一:當數組已經有序時不能及時推出,而是依然判斷是否交換。
        缺點兒:當無序數列尾部存在部分有序時任然對有序部分進行判斷是否交換。
 
      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.從第一個元素開始,該元素可以認為已經被排序;

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

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

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

         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)。

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM