排序(1)--插入排序和交換排序

fan_rockrock發表於2014-02-23

首先引入幾個概念:

         內部排序:整個排序過程不需要訪問外存便能完成。

         外部排序:參加排序的記錄數量很大,整個序列的排序過程不可能在記憶體中完成。

         穩定性和不穩定性: 設 Ki、Kj (1≤i≤n, 1≤j≤n, i≠j ) 分別為記錄 Ri、Rj 的關鍵字,且 Ki = Kj ,在排序前的序列中 Ri 領先於 Rj (即 i < j )。若在排序後的序列中 Ri 仍領先於 Rj                                               ,則稱所用的排序方法是穩定的;反之,則稱所用的排序方法是不穩定的。 


一.插入排序:

          1.直接插入排序:

                    (1)思想: 對第i個元素,把它插入到前i-1個元素(已經有序)裡面(2<=i<=n)

                    (2).實現:                            

void InsertSort(SqList &L)
{
    //對順序表L做直接插入排序
    for(i=2;i<=L.length;i++)
    {
       if(L.r[i].key<L.r[i-1].key)
       {
          L.r[0]=L.r[i];
          L.r[i]=L.r[i-1];
          for(j=i-2;L.r[0].key<L.r[j].key;j--)
          {
             L.r[j+1]=L.r[j];//記錄後移
          }
          L.r[j+1]=L.r[0];//插入到正確位置
       }
    }
}
                   (3).演算法效能分析: 

                              時間複雜度:最好情況:全部有序,那麼移動次數為0;

                                                      最壞情況:全部逆序,移動次數為

                                                      取移動次數的平均數得演算法時間複雜度為:O(n2)

                              穩定性:鍵碼相同的兩個記錄,在整個排序過程中,不會通過比較而相互交換,所以是穩定的。

            2.折半插入排序:

                    (1)思想:考慮到 L.r[1,..,i-1] 是按關鍵字有序的有序序列,則可以利用折半查詢實現“L.r[1,…,i-1]中查詢 L.r[i] 的插入位置”如此實現的插入排序為折半插入排序。

                    (2).實現:                       

void BinsertSort(SqList &L){  // 折半插入排序
    int i,low,high,mid;
    for(i=2; i<= L.length; ++i) {
       L.r[0]=L.r[i];                 //將L.r [i] 暫存到L.r[0]
       low=1;  high=i-1;
       While(low<=high) {     //比較,折半查詢插入位置
           mid=(low+high)/2;   // 折半
           if (L.r[0].key< L.r[mid].key) high=mid-1;   //插入點在低半區
           else low=mid+1; }   // 插入點在高半區
       for( j=i-1; j>=low; --j )  L.r[j+1]=L.r[j];    // 記錄後移
       L.r[low]=L.r[0]; }    // 插入
} // BInsertSort
                    (3).演算法效能分析:

                                時間複雜度: 折半查詢比順序查詢快,所以折半插入排序就平均效能來說比直接插入排序要快。
                                                         折半插入排序減少了關鍵數的比較次數,但記錄的移動次數不變,其時間複雜度與直接插入排序相同。

                               穩定性:穩定。

               3.希爾插入排序:

                      (1)思想:先將整個待排記錄序列分割成若干子序列,分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行一次直接插入排序。

                                       子序列的構成不是簡單地“逐段分割”,而是將相隔某個增量dk的記錄組成一個子序列,讓增量dk逐趟縮短(例如依次取5,3,1),直到dk=1為止

                      (2)實現:                    

void ShellInsert ( SqList &L, int dk )
{  //一趟希爾插入排序
   //1.前後記錄位置的增量是dk;
   //2.L.r[0]只是暫存單元,不是哨兵。當j<=0時,插入位置已找到
 for ( i=dk+1; i<=L.length; ++i )
   if ( L.r[i].key< L.r[i-dk].key) {//將R[i]插入有序增量子表
      L.r[0] = L.r[i]; // 暫存在R[0]
      for (j=i-dk; j>0 && (L.r[0].key< L.r[j].key);j -= dk)
         L.r[j+dk] = L.r[j]; // 記錄後移,查詢插入位置
      L.r[j+dk] = L.r[0]; // 插入 
   }
}//ShellInsert
void ShellSort (SqList &L, int dlta[ ], int t)
{
    // 按增量序列dlta[0..t-1]對順序表L作希爾排序
    for (k=0; k<t; ++k)
        ShellInsert( L, dlta[k]);  // 一趟增量為dlta[k]的插入排序
} // ShellSort

                       (3)效能分析:

                                    開始時dk 的值較大,子序列中的物件較少,排序速度較快;隨著排序進展,dk 值逐漸變小,子序列中物件個數逐漸變多,由於前面工作的基礎,大多數對                                     象已基本有序,所以排序速度仍然很快。

                                時間複雜度:——所選增量比較合理

                                空間效率:O(1) —— 因為僅佔用1個緩衝單元

                                穩定性:希爾排序選取增量的時候都是一個一個子序列比較的,很容易導致後一個數排到前一個數前面去,所以不穩定!


二.交換排序:

       1.氣泡排序:

             (1)思想:每一輪都是兩兩相鄰比較,直到小的浮起或大的沉底

              (2)實現:                      

void Bubblesort(ElemType R[],int n) { 
    int flag=1;  //當flag為0則停止排序
    for  (int i=n; i>1; i--)  {  //i表示趟數,最多n-1趟
        flag=0;  //開始時元素未交換
        for (int j=2; j<=i; j++)  
           if (R[j]<R[j-1]) {    //發生逆序
               temp=R[j];   R[j]=R[j-1];  R[j-1]=temp; 
               flag=1;
           } 
        if(flag==0) return;    
    }
} // Bubblesort
               (3)演算法分析:

                     時間複雜度:O(n2),可以取最好情況和做差情況取平均

                     穩定性:穩定

               起泡排序的過程可見,起泡排序是一個增加有序序列長度的過程,也是一個縮小無序序列長度的過程,每經過一趟起泡,無序序列的長度只縮小1。
              試設想:若能在經過一趟排序,使無序序列的長度縮小一半,則必能加快排序的速度。

          2.快速排序:

                 (1)思想:通過一趟排序將待排序列以樞軸為標準劃分成兩部分,使其中一部分記錄的關鍵字均比另一部分小,另一部分大,再分別對這兩部分進行快速排序,以達到                                     整個序列有序(通常取第一個記錄的值為基準值或樞軸).

                 (2)實現:  

void QSort ( Elem R[ ], int low, int high ){  //對序列R[low...high]進行快速排序
   if (low < high-1) {    //長度大於1
      pivot = Partition( L,low,high);  //將R[low..high]一分為二
      QSort(L,low, pivot-1);    //對低子表遞迴排序,pivo是樞軸
      QSort(L, pivot+1, high);   // 對高子表遞迴排序
      }
} // QSort
void QuickSort(Elem R[], int n){   //對記錄序列進行快速排序
     QSort(R, 1, n);
} // QuickSort
int Partition (Elem R[ ], int low, int high){
     R[0] = R[low];   pivotkey = R[low].key;  
     while (low < high) { //從兩端交替向中間掃描
         while (low < high && R[high].key >= pivotkey) - - high;
         R[low] = R[high];  //將比樞軸記錄小的移到低端
         while (low < high && R[low].key <= pivotkey)  + + low;
         R[high] = R[low];  //將比樞軸記錄大的移到高階
    } 
    R[low] = R[0];   //樞軸記錄到位
    return low;    //返回樞軸位置
} // Partition
                 (3)演算法效能分析:

                     時間複雜度:

                     空間複雜度:快速排序是遞迴的,需要有一個棧存放每層遞迴呼叫時的指標和引數(新的low和high)。最大遞迴呼叫層次數與遞迴樹的深度一致,理想情況為                                                              └ log2n ┘ + 1(向下取整) 。因此,要求儲存開銷為 O(log2n)

                     穩定性:不穩定。
                    

相關文章