數據結構-排序

排序

考慮大規模堆數據排序

只考慮內部排序:內存充分大,所有數據都能加載到內存中

排序的穩定性:任意兩個相等的數據,排序前後的相對位置不發生改變

沒有一種排序是任何情況下都是表現最好的

逆序對

  • 對於下標i<j,如果a[i]>a[j],則(i,j)爲逆序對(inversion)
  • 交換兩個相鄰元素正好消去一個逆序對
  • 任意N個不同元素組成對序列平均具有N(N1)/4N(N-1)/4個逆序對。
  • 任意一個以交換兩個相鄰元素對排序算法,平均時間複雜度爲Ω(N2)\Omega(N^2)(下界,最好情況)
  • 要提高算法效率,每次需要消去不止一個逆序對,每次要交換兩個較遠的元素。

以下默認int型升序排序

冒泡排序

int i,j;
for(i=0;i<N;i++){
   for(j=0;j<N-i-1;j++){
      ......
   }
}

for(i=N-1;i>=0;i--){
   for(j=0;j<i;j++){
       if(a[j]>a[j+1]){
           swap(a[j],a[j+1]);
           flag = 1;
       }
       if(!flag)// 如果一趟下來沒有交換,則已經全部排好。
         break;
   }
}

最好T=O(n)T=O(n),最壞T=O(n2)T=O(n^2) ,穩定
後面是有序的,前面無序。
便於鏈表排序

插入排序

for(i=1;i<N;i++){//默認一個元素是有序的
tem = a[i];
  for(j=i;j>0 && a[j-1]>tem;j--){
     a[j] = a[j-1];
  }
  a[j] = tem;
}

最好T=O(n)T=O(n),最壞T=O(n2)T=O(n^2) ,穩定
前面是有序的,後面無序。比冒泡排序交換次數少。

若序列基本有序,則插入簡單高效

希爾排序(shell)

爲了提高算法效率,將每次交換的元素間隔拉大。

  • N間隔排序:對i,i+N,i+2N,…,i+nN這些元素進行排序,然後再對i+1做上述操作,直到i=N。每次調整的是整個序列的一個子序列。然後將N變小,重複上述操作。爲了保證序列有序,最後N必須等於1。
  • N間隔排序後,在進行(N/2)間隔排序後,依然保持N間隔有序。
//原始希爾排序
for(i=N/2;i>0;i/=2){// i爲間隔
   for(j=i;j<N;j++){ // 插入排序
       tem = a[j];
       for(k=j;k>=0 && a[k-i]>tem;k-=i){
           a[k]=a[k-i];
       }
       a[k]=tem;
   }
}

最壞情況 T=θ(N2)T=\theta(N^2) (既是上界又是下界,即等價)
由於間隔序列不互斥,會導致小的間隔無效(即無元素交換)
Hibbard增量序列
Dk=2k1D_k=2^k-1相鄰元素互斥
swdgewick增量序列
4i32i+14^i-3*2^i+1
效果較好

選擇排序

for(i=0;i<M;i++){
  for(j=i;j<N;j++)
     if(a[j]<min)
        min = a[j];
     swap(a[i],min);
}

T=θ(N2)T=\theta(N^2)
選擇排序在交換兩元素時不一定是交換相鄰兩個元素,故破壞了穩定性

堆排序

  1. 建堆 T(N)=O(NlogN)T(N)=O(NlogN) ,需要額外空間O(n)O(n),且複製元素需要時間
  2. 建最大堆,將最大元素放到當前堆末尾。平均比較次數2NlogNO(NloglohN)2NlogN-O(NloglohN)

實際效果不如swdgewick增量序列堆希爾排序

歸併排序

在這裏插入圖片描述

核心:有序子列堆歸併
T(N)=O(N)T(N)=O(N),但元素需要不停但複製,且需要額外的空間,不常用於內排序,常用於外排序。
穩定的排序

// 遞歸  

void M(int a[],int tem[],int l,int r,int e){
    int tem = l,m = r-1,beg= l;
    while(l<= m && r<= e){
      if(a[l]  <= a[r]){ //保證穩定性
         tem[in]=a[l];
         l++
      }elsr{
         tem[in]= a[r];
         r++;
      }
       in++;
    }
 while(l<=m){
   tem[in]=a[l];
   in++;
   l++;
 }
 while(r<=e){
   tem[in]=a[r];
   r++;
   in++;
 }

    for(i=beg;i<=e;i++)// 將數據倒回原來倒數組
       a[i]=tem[i];
}

void sort(int a[],int tem[],int b,int e){
   int m = (b+e)/2;
   if(b<e){
       sort(a,tem,b,m);
       sort(a,tem,m+1,e);
       m(a,tem,a,m+1,e);
   }
}

// 非遞歸
// 合併子列
void M1(int a[],int tem[],int l,int r,int e){
     int m=r-1,in= l;
     while(l<=m && r<=e){
       if(a[r]>a[l]){
          tem[in] = a[r];
          r++;
       }else{
         tem[in] = a[l];
         l++;
       }
       in++;
     }
     while(r<=e){
       tem[in]= a[r];
       in++;
       r++;
     }
     while(l<=m){
      tem[in]= a[l];
      in++;
      l++;
     }
}
// 合併長度爲len的全部子列
void M_pass(int a[],int tem[],int n,int len){
   for(i=0;i<n-2*len;i+=2*len)
      M1(a,tem,i,i+len,i+2*len-1);
    if(i+len <n)//最後剩一個完整倒和一個不完整的字串
      M1(a,in,i,i+len,n-1);
     else// 只剩一個不完整的字串
       for(j=i;j<n;j++)
         tem[j] = a[j];
}

void sort(int a[],int N){
   int tem[N],len=1;
   while(len<N){
      M_pass(a,tem,N,len);
      len *=2;
      M_pass(a,tem,N,len);
   }
}

快排

不是永遠都是最好的。
且若實現的不好,則很慢

分而治之

選主元 pivot ,即選出中間數

當最好情況是主元是中位數,最壞是有序的。
選主元可以從頭,中,尾三個數中取中位數。

快排快的原因之一,是主元直接放到了最終的位置上。

快排是用遞歸,不適合小規模數據。對於N<100,可能不如插入。
定義一個cutoff,小於該閾值,使用其他排序,如簡單排序

//利用首,中,尾三個元素選主元
int median3(int a[],int l,int r){
    int m = (r+l)/2;
    // 保證左面最小,右面最大,實現排序
    if(a[l]>a[m])
      swap(a[l],a[m]);
    if(a[l]>a[r])
      swap(a[l],a[r]);
    // 以上保證左面最小,以下保證右面最大
    if(a[m]>a[r])
      swap(a[m],a[r]);
    swap(a[m],a[r-1]);
    // 下一步劃分子集時,只需要考慮l+1,r-2之間的元素
    return a[r-1];
}
void q_sort(int a[],int l,int r){
int p;
   if(r-l >cutoff){// 使用快排
       p = median3(a,r,l); //選主元
       i=l ;
       j = r -1; 
       // 待劃分的區域是i+1到r-2
        for(;;){
          while(a[++i]<p){}
          while(a[--j]>p){}
          if(i<j)
             swap(a[i],a[j]);
           else 
             break;
       }
   }else{ //規模太小,使用其他排序,如插入排序
        other_sort(a,l,r);
   }
}

void sort(int a,int l){
   q_sort(a,0,l-1);
}

表排序

對比較複雜對元素進行排序,如複雜的結構體,不能不考慮交換元素之間的時間。只是移動對應的指針。是間接排序,利用一個新數組做爲表(table),表中存放實際元素對應的指針,只需將指針排序即可。

若需要將元素按順序排,而不僅僅是排指針,則需要移動元素,如何使移動元素所消耗時間最短是要解決的問題。下面給出線型時間複雜度對移動算法。
N個數字的排列是由若干個環組成的,如下圖,紅色爲一個環,綠色爲一個環,藍色爲一個環。
在這裏插入圖片描述
每次移動只對一個環內元素進行移動,首先需要保存環上第一個元素對應對數據,將其他元素調到對應對位置,每次移動一個元素後,將其table值設爲對應對下標,即table[i]=i;當遇到table[i]=i時,一個環結束。

最好情況,有序
最壞情況:每個環有兩個元素。

基數排序

實現某種情況下的線性時間複雜度。

桶排序

n個數據排序,只有m種取值,且n>>m,建立n個桶,掃描一次n個數據,將數據放到對應的桶中,近似線性。

當m>>n時,需要基數排序,實現近似線性複雜度。
當n=10,m=1000,使用次位優先(Least Significant digit)。用最低位進行排序,幾位數決定需要排幾次,每次拍完後,按序去取出在這裏插入圖片描述
也可以對應多種排序方式

總結

在這裏插入圖片描述
一次後位置固定:冒泡,插入

reference

浙大 數據結構 mooc https://www.icourse163.org/course/ZJU-93001

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章