1. 程式人生 > 實用技巧 >排序專題(未更新完)

排序專題(未更新完)

目錄

排序概述


  排序就是給定一些資料的集合,需要將它們按照從大到小或者從小到大的順序輸出。將這個過程稱為排序,這樣的演算法稱為排序演算法。
  排序的演算法定義如下:
   輸入:n個記錄R1,R2,R3,......,Rn,對應的關鍵字為k1,k2,k3,.....kn;
   輸出:經排序後得到的整數,R1',R2',R3',....,Rn' 對應的關鍵字k1'<k2'<k3'<....<kn';(也可以是>,也可能相等);
  在排序演算法中,是十分注重演算法效能的,因為對於很大資料集的演算法,排序演算法的好壞對時間上有很大的影響,我們一般從3個方面去評判一個排序演算法。
  1. 時間複雜度:
  2. 空間複雜度:
  3. 穩定性:如果排序過程中不改變2個相等元素的相對位置則是穩定的。


  通常將排序演算法分為內部排序和外部排序,內部排序演算法主要是在記憶體上進行,相對於處理的處理的資料較小。而外部排序通常資料是存放在外存上的,只有在排序過程中,將其中一部分準備排序的資料塊讀入記憶體,然後進行內部排序。因為待排序的資料通常是無法一次性讀到記憶體的。

內部排序

插入排序

直接插入排序

  直接插入排序的思想很簡單,就是每次將待排序的元素插入到已經排序好的序列中。在初始時,將第一個元素看成是有序的,然後依次掃描後面的所有的元素,每次將掃描到的元素插入到前面已經排好序列合適的位置。
  如我們對下列序列進行排序:

0 1 2 3 4 5 6 7 8 9
9 7 8 2 5 1 3 6 4

初始時將9看錯已經排好序的子序列,然後開始掃描後續元素,掃描到的元素和前面元素作比較,若是小於前面元素就繼續向前掃描。直到最後一個元素被插入序列中。

void InsertSort( ElemType A[], int n){
       int i. j; 
       for(int i = 2; i<=n ; i++){
            if(A[i] < A[i-1]){
                  A[0] = A[i];
                  for(int j = i-1; A[j] > A[0]; j--){
                        A[j+1] = A[j];
                  }
                  A[j+1] = A[0];
            }
       }
}

空間效率:不需要額外申請空間,所以是 O(1)
時間效率:每次都需要向前比較,總共有n個元素,所以時間複雜度時 O(n²)
穩定性: 穩定的演算法;

折半插入排序

  折半插入排序的思想:就是將折半插入排序分為2步,①從已排好序的序列中查詢到待插入元素的位置,②然後將元素插入到該位置。而折半插入演算法就是將第一步採用折半查詢的方式找到待插入的位置,然後進行插入。因為使用了折半查詢,所以演算法只能處理順序儲存結構。


void  InsertSort(ElemType A[], int n){
        int i, j , low , high , mid ;
        for(int i = 2; i <=n ; i++){
             A[0] = A[i];
             low = 1, high = i-1;
             while(low < high){
                   mid = (low + high)/2;
                   if( A[mid] > A[0] ) high = mid -1;
                   else low = mid +1;
             }
             for(int j = i-1; j>= high+1; --j){
                A[j+1] = A[J];
             }
             A[high] = A[0];
        }
}


空間複雜度:O(1);
時間複雜度:折半查詢需要O(log₂n),而移動元素需要O(n) ,所以一共需要 O(nlog₂n)。
穩定性: 穩定的演算法

希爾排序

  希爾排序的思想:先將排序序列分為若干步長為d(i)的特殊的子表,然後分別對資子表進行直接插入排序。然後對步長進行縮短,讓d(i+1) = d(i)/2,直到最後d=1;此時表已經基本有序,直接插入排序對於基本有序的表進行排序時效率很高。


void ShellSort(ElemType A[], int n){
        for(int dk = n/2; dk >=1 ; dk = dk/2){  //控制步長變化
            for(int i=dk+1; i<= n; ++i){         // 對每個子表進行插入排序
                if( A[i] < A[I-dk]){                //判斷是否小於子表前面的元素
                    A[0] = A[i];
                    for(j = i-dk ; j>0&&A[0]<A[j]; j-=dk){ //移動元素
                           A[j+dk] = A[j];
                     }
                     A[j+dk] = A[0];
                }
           }
        }
}

空間複雜度:O(1)。
時間複雜度:當n在某個範圍時 時間複雜度為O(n1.3)),最壞情況下為O(n2);
穩定性:不穩定

插入排序

氣泡排序

  氣泡排序的思想:從後往前兩兩比較相鄰元素的值,若為逆序,則交換他們的值,直到序列有序為止。每一趟氣泡排序都會將一個最小的元素放到最終的位置上。然後縮短排序序列,直到整個序列有序。為了優化排序效率,我們可以設定一個標誌,若是某一趟排序沒有元素交換,就說明序列已經有序了。


void bubbleSort(ElemType A[], int n){
       for(int i =0; i<n-1; i++){
            flag = false;
            for (int j = n-1; j>i; j--){
                if( A[j] < A[j-1] ){
                    swap(A[j], A[j-1]);
                    flag = true;
            }
            if(flag = false){
                return ;
            }
       }

}

空間複雜度:O(1);
時間複雜度:O(n^2)
穩定性:穩定

快速排序

  快速排序的思想:快速排序是一種基於分治思想的演算法,在序列中選取一個元素作為中軸元素pivot,然後將小於pivot的元素放到pivot的左邊,所有大於pivot的元素放到pivot的右邊。然後再對pivot左右2邊區域再進行快速排序,直到整個序列有序。每次快速排序,都會把pivot元素放到最終位置。


void QuickSort(ElemType A[] , int low , int high){
    if( low < high){
        //Partition()就是一次劃分操作,將表A[low, high]劃分為滿足上述條件的兩個子表
        int pivotpos = Partition(A, low, high); 
        QuickSort(A, low, pivotpos-1);
        QuickSort(A,pivotpos+1 , high);
     }
}

int Partition(ElemType A[], int low ,int high){
     ElemType pivot = A[low];
     while( low < high){
        while(low <high && A[high] >= pivot) --high;
        A[low] = A[high];
        while(low< high && A[low]  <= pivot) ++low;
        A[high] = A[low];
     }
     A[low] =  pivot;
     return low;
}

空間複雜度:演算法遞迴,所以需要藉助遞迴工作棧,空間複雜度最壞為O(n);最好為O(logn);平均為O(logn);
時間複雜度:最壞情況下,時間複雜度為O(n^2), 如果最好的情況下,劃分函式可以中心劃分,則時間複雜度為O(nlogn);
快速排序是所有內部排序演算法中平均效能最優的演算法。

選擇排序

簡單選擇排序

  簡單選擇的思想:每次從待排序序列中選擇最小的元素,然後加入已排好序列的末尾作為第i個元素。直到待排序列中剩下一個元素則是最大的元素,所以序列有序。

viod SelectSort(ElemType A[], int n ){
        for(int i=0; i<n-1;i++){
            int min =i;
            for(int j=i+1; j<n;j++){
                if(j]>A[min])min = j;
            }
            if(min!=i) swap(A[i],A[min]);
        }
}

空間複雜度:O(1)
時間複雜度:O(n^2)
穩定性:不穩定;

堆排序

  對於堆排序,先來了解下什麼是堆?
  堆就是一種樹形結構,若用樹的順序結構儲存的話,它滿足:
    ①L[i] >= L[2i] 且 L[i] >= L[2i+1],稱之為大根堆;
    ①L[i] <= L[2i] 且 L[i] <= L[2i+1],稱之為小根堆;

堆排序的思路很簡單,首先將存放在L[1.....n]中的元素構建稱為初始堆,然後利用堆的根結點一定是最大值結點,輸出根節點,輸出根節點後,將最尾部元素充當根節點,此時大根堆已經不滿足上述條件,需要對堆重新調整使它重新稱為大根堆。一直到元素都輸出為止。

  建立堆和輸出結點後排序重新調整堆都是建立在調整堆的,所以先看怎樣調整一顆任意的二叉樹為一個大根堆。堆的每次調整都是從最後一個非葉子結點(m/2)開始調整的,檢查m/2結點是否小於左右孩子結點的最大值,若小於,將孩子節點的最大值和它互換。互換後可能會使孩子結點作為父節點的3個結點出現孩子結點大於父節點的現象,所以需要一直向下調整,直到不需要調整為止。之後去調整m/2-1這個結點,直到根節點為止。


//演算法主要從非葉子結點k開始進行調整。
void HeadAdjust( ElemType A[], int k, int len){
        A[0] = A[k];
        for(int i = 2*k, i<= len; i *=2){
            if(i<len && A[i] < A[i+1]{
                i++;
            }
            if(A[0] >= A[i]) break;
            else{
                A[k] = A[i];
                k = i;
            }
        }
        A[k] = A[0];
}

//建立堆, 將陣列長度為len的A[] 建立起大根堆
void BuildMaxHeap(ElemType A[] , int len){
       for(int i = len/2; i>0;i--){
            HeadAdjust(A,i,len);
       }
}


//堆排序演算法 ,每次把根節點摘下和最後一個結點交換,然後刪除最後一個結點,然後重新構建堆。
void HeapSort(ElemType A[], int len){
        BuildeMaxHeap(A,len);
        Swap(A[i],A[1]);
        HeadAdjust(A,1,i-1);
}

空間複雜度:O(1);
時間複雜度: O(nlogn)
穩定性:不穩定;

外部排序