內部排序算法總結

內部排序算法總結

 

排序算法介紹

排序算法主要可以分爲內部排序和外部排序,其中內部排序由可以分爲非線性時間比較排序和線性時間非比較排序,前者主要通過比較來決定元素之間的相對次序,都有關鍵字的比較和記錄的移動,其時間複雜度不能突破O(nlogn),後者不需要通過比較來進行,可以以線性時間運行。

排序算法分類

非線性時間比較排序:  
      插入排序:簡單插入排序、折半插入排序、希爾排序
      交換排序:冒泡排序、快速排序
      選擇排序:直接選擇排序、堆排序
      歸併排序:二路歸併排序、多路歸併排序(下文未介紹)

線性時間非比較排序:
      基數排序、計數排序、桶排序

不穩定排序:希爾排序、快速排序、直接選擇排序、堆排序
其餘爲穩定排序


複雜度&&穩定性

內部排序算法的複雜度&&穩定性
排序算法 平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性
簡單插入排序 O(n^2) O(n) O(n^2) O(1) 穩定
折半插入排序 O(n^2) O(n) O(n^2) O(1) 穩定
希爾排序 O(n^(1.3)) O(n) O(n^2) O(1) 不穩定
冒泡排序 O(n^2) O(n) O(n^2) O(1) 穩定
快速排序 O(nlogn) O(nlogn) O(n^2) O(logn) 不穩定
直接選擇排序 O(n^2) O(n^2) O(n^2) O(n^2) 不穩定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不穩定
二路歸併排序 O(nlogn) O(nlogn) O(nlogn) O(n) 穩定
基數排序 O(d(n+r)) O(d(n+r)) O(d(n+r)) O(rd) 穩定
計數排序 O(n+k) O(n+k) O(n+k) O(n+k) 穩定
桶排序 O(n+k) O(n+k) O(n^2) O(k) 穩定

 

 

代碼

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e5 + 10;

/*
注:
1.數組中的元素默認放在從a[1]開始
2.以下排序都是基於順序存儲實現,非鏈式,有些算法可以修改成鏈式
3.總共有以下幾類排序
1)直接插入排序
2)折半插入排序
3)希爾排序
           (1-3屬於插入排序)
4)冒泡排序
5)快速排序
           (4-5屬於交換排序)
6)直接選擇排序
7)堆排序
           (6-7屬於選擇排序)
8)二路歸併排序
           (8屬於歸併排序)
           (1-8屬於非線性時間比較排序)
9)基數排序
10)計數排序
11)桶排序
           (9-11屬於線性時間非比較排序)
*/

/*
直接插入排序
平均時間複雜度:O(n^2)
最好時間複雜度:O(n)   初始序列爲正序
最壞時間複雜度:O(n^2) 初始序列爲逆序
空間複雜度:O(1)
穩定性:穩定
*/
void Straight_Insert_Sort(int *a, int n)
{
  for(int i = 2; i <= n; i++)
    {
      if(a[i] < a[i-1])
      {
        int j;
        a[0] = a[i]; //設置哨兵
        for(j = i-1; a[j] > a[0]; j--)
           a[j+1] = a[j];
        a[j+1] = a[0];
      }
    }
}

/*
折半插入排序
平均時間複雜度:O(n^2)
最好時間複雜度:O(n)   初始序列爲正序
最壞時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:穩定
注:折半插入排序是插入排序的一個優化算法,主要是減少了尋找插入位置時的比較次數,但是元素的移動次數仍然不變,所以總的時間複雜度仍然是O(n^2)
*/
void Binary_Insert_Sort(int *a, int n)
{
  for(int i = 2; i <= n; i++)
   {
     if(a[i] < a[i-1])
     {
       a[0] = a[i];
       int l = 1, r = i-1;
       while(l <= r)
       {
         int m = l + (r-l)/2;
         if(a[m] <= a[0]) l = m + 1;
         else r = m - 1;
       }
       int j;
       for(j = i-1; j >= l; j--)
           a[j+1] = a[j];
       a[j+1] = a[0];
     }
   }
}

/*
希爾排序
平均時間複雜度:O(n^(1.3—2))
最好時間複雜度:O(n)   初始序列爲正序
最壞時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:不穩定
注:
1.希爾排序也屬於插入排序的一種,又稱“縮小增量排序”(Deminishing Increment Sort)
基本思想是將數列等步長分成若干組,組內進行插入排序,步長不斷減小,直至1,這樣就可以減少參與直接插入排序的數據量,有利於減小比較和移動的次數,從而縮減時間複雜度。
其時間複雜度和選取得增量序列有關
希爾增量: ht = N / 2, h[k] = h[k+1] / 2,即{N/2, (N / 2)/2, ..., 1}
Hibbard:{1, 3, ..., 2^k-1};
Sedgewick:{1, 5, 19, 41, 109...}該序列中的項或者是9*4^i - 9*2^i + 1或者是4^i - 3*2^i + 1。
2.以下代碼非使用監視哨算法,使用監視哨算法可以縮減一定的測試查找循環時間,解決邊界問題。但是估計會需要使用O(inc)的代價。
*/

void Shell_Sort(int *a,int n)
{
 int inc = n;
 do {
   inc = inc/3 + 1;
   for(int i = 1+inc; i <= n; i++)
   {
     if(a[i] < a[i-inc])
    {
      int j;
      a[0] = a[i];
      for(j = i-inc; j > 0 && a[j] > a[0]; j -= inc)
            a[j+inc] = a[j];
      a[j+inc] = a[0];
    }
  }
  } while(inc > 1);
}


/*
冒泡排序
平均時間複雜度:O(n^2)
最好時間複雜度:O(n)   初始序列爲正序
最壞時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:不穩定
*/

// 最簡單的交換排序,嚴格上不算冒泡排序,不滿足“兩兩比較相鄰記錄”
void Bubble_Sort_A(int *a, int n)
{
  for(int i = 1; i < n; i++)
    for(int j = i+1; j <= n; j++)
       if(a[i] > a[j]) swap(a[i],a[j]);
}

//冒泡排序
void Bubble_Sort_B(int *a, int n)
{
  for(int i = 1; i < n; i++)
    for(int j = n-1; j >= i; j--)
      if(a[j] > a[j+1])
         swap(a[j],a[j+1]);
}

//優化後的冒泡排序
void Bubble_Sort_C(int *a, int n)
{
  bool flag = true;
  for(int i = 1; i < n && flag; 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;
       }
    }
}


/*
快速排序
平均時間複雜度:O(nlogn)
最好時間複雜度:O(nlogn)
最壞時間複雜度:O(n^2) 初始序列爲正序
空間複雜度:O(logn)
穩定性:穩定
注:
1.快速排序是通過一趟排序將序列分爲兩部分,一部分均比關鍵字小,一部分均比關鍵字大,
然後再對這兩部分分別進行快速排序。
2.時間複雜度分析
我們來利用Master主定理分析
Master 主定理:  對於T[n] = aT[n/b] + f(n) 並且f(n) 是一個漸進正函數,則有一下三種情況:
1)如果f(n) = O(n^(logb(a-c))) 對於某個常量c>0成立,那麼T[n] = O(n^(logb(a)))
2)如果f(n) = O(n^(logb(a))), 那麼T[n] = O(n^(logb(a)) * logn)
3)如果f(n) = O(n^(logb(a+c))) 對於某個常量c>0成立,並且af(n/b) <= cf(n) 對於某個常量c < 1 (n足夠大) 成立, 那麼T[n] = O(f(n))

對於快速排序,T[n] = 2T[n/2] + f(n)
a = 2, b = 2 , logb(a) = 1; 所以 f(n) =  O(n^(logb(a))) = n; 所以T(n) = n*logn

最壞情況時, 在原序列有序時,T[n] = T[n-1] + T[1] + O(n)
對於這種情況,主觀分析:因爲每次只排出一個,相當於沒做,所以複雜度爲O(n^2))
3.快速排序的空間複雜度O(logn)主要是因爲遞歸調用需要保持一些數據
*/

// 0,l,r 相互配合組成哨兵
void Quick_Sort(int *a, int L, int R)
{
  if(L >= R) return ;
  a[0] = a[L];
  int l = L, r = R;
  while(l < r)
  {
    while(l < r && a[r] >= a[0]) r--;
    a[l] = a[r];
    while(l < r && a[l] <= a[0]) l++;
    a[r] = a[l];
  }
  a[l] = a[0];
  Quick_Sort(a, L, l-1);
  Quick_Sort(a, l+1, R);
}

/*
直接選擇排序
平均時間複雜度:O(n^2)
最好時間複雜度:O(n^2)
最壞時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:不穩定
*/

void Selec_Sort(int *a, int n)
{
  for(int i = 1; i < n; i++)
    {
      int tag = i;
      for(int j = i+1; j <= n; j++)
        if(a[j] < a[tag]) tag = j;
      swap(a[i],a[tag]);
    }
}

/*
堆排序
平均時間複雜度:O(nlogn)
最好時間複雜度:O(nlogn)
最壞時間複雜度:O(nlogn)
空間複雜度:O(1)
穩定性:不穩定
*/
//s 是子堆的起點,m 是堆的數據量
void Heap_Adjust(int *a, int s, int m)
{
  a[0] = a[s];
  for(int i = 2*s; i <= m; i <<= 2)
  {
    //比較左右節點的大小
    if(i < m && a[i+1] > a[i])
      i++;
    if(a[i] <= a[s]) break;
    a[s] = a[i];
    s = i;
  }
  a[s] = a[0];
}

void Heap_Sort(int *a, int n)
{
  for(int i = n/2; i > 0; i--)
    Heap_Adjust(a,i,n);
  for(int i = n; i > 1; i--)
  {
    swap(a[1],a[i]);
    Heap_Adjust(a,1,i-1);
  }
}

/*
歸併排序(二路)
平均時間複雜度:O(nlogn)
最好時間複雜度:O(nlogn)
最壞時間複雜度:O(nlogn)
空間複雜度:O(n)
穩定性:穩定
*/

void Merge_Sort(int *a, int L, int R)
{
  if(L >= R) return ;
  int m = L + (R-L)/2;
  int n1 = m-L+1, n2 = R-m;

  Merge_Sort(a,L,m);
  Merge_Sort(a,m+1,R);

  int *l = (int*)malloc(sizeof(int)*(n1));
  int *r = (int*)malloc(sizeof(int)*(n2));

  for(int i = 0; i < n1; i++)
    l[i] = a[L+i];
  for(int i = 0; i < n2; i++)
    r[i] = a[m+i+1];

  int i = 0,j = 0,k = L;
  while(i < n1 && j < n2)
      a[k++] = l[i] <= r[j] ? l[i++] : r[j++];
  while(i < n1)
      a[k++] = l[i++];
  while(j < n2)
      a[k++] = r[j++];
  free(l);
  free(r);
}

/*
基數排序
平均時間複雜度:O(d(n+r))
最好時間複雜度:O(d(n+r))
最壞時間複雜度:O(d(n+r))
空間複雜度:O(rd)
穩定性:穩定
注:不能對負數進行處理,如果要處理負數可以統一先加一個大整數,最後再統一減去。
d個關鍵碼,關鍵碼的範圍是0-r
*/

void Radix_Sort(int *a, int n)
{
  int maxa = a[1],d = 0,exp = 1;
  int *count = new int[n+1];
  for(int i = 2; i <= n; i++)
     maxa = max(maxa,a[i]);
  while(maxa) maxa /= 10,d++;
  for(int i = 1; i <= d; i++, exp *= 10)
    {
      int range[10] = {0};
      for(int i = 1; i <= n; i++)
        range[a[i]/exp%10]++;
      for(int i = 1; i <= 9; i++)
        range[i] += range[i-1];
      for(int i = n; i > 0; i--)
        count[range[a[i]/exp%10]--] = a[i];
      for(int i = 1; i <= n; i++)
        a[i] = count[i];
    }
  delete[] count;
}

/*
計數排序
平均時間複雜度:O(n+k)
最好時間複雜度:O(n+k)
最壞時間複雜度:O(n+k)
空間複雜度:O(n+k)
穩定性:穩定
注:計數排序是一個非基於比較的算法,前面的算法中,除了基數排序,其他排序都是基於比較的算法,都有關鍵字的比較和記錄的移動,基於比較的的排序算法的時間複雜度是O(nlogn),如堆排序和歸併排序。計數排序的時間複雜度是O(n+k),k是數據的範圍,其快於任何一種基於比較的算法,其是犧牲空間換取時間的做法,當O(k) > O(nlogn)時反而慢於基於比較的排序算法。
前面的基數排序也是基於基數排序實現的,只不過其k <= 9。
計數排序的穩定性:range[i] 表示小於等於i的元素個數,所以倒着遍歷保證穩定性。
*/

void Count_Sort(int *a, int n)
{
  int k = a[1];
  for(int i = 2; i <= n; i++)
     k = max(k,a[i]);
  int *range = new int[k+1]();
  int *count = new int[n+1];
  for(int i = 1; i <= n; i++)
    range[a[i]] ++;
  for(int i = 1; i <= k; i++)
    range[i] += range[i-1];
  for(int i = n; i >= 1; i--)
    count[range[a[i]]--] = a[i];
  for(int i = 1; i <= n; i++)
    a[i] = count[i];
  delete[] range;
  delete[] count;
}

/*
桶排序
平均時間複雜度:O(n+k)
最好時間複雜度:O(n+k)
最壞時間複雜度:O(n^2)
空間複雜度:O(k)
穩定性:穩定
*/

void Bucket_Sort(int *a, int n)
{
  int k = a[1];
  for(int i = 2; i <= n; i++)
    k = max(k,a[i]);
  int *range = new int[k+1]();
  for(int i = 1; i <= n; i++)
    range[a[i]]++;
  int idx = 1;
  for(int i = 0; i <= k; i++)
    while(range[i]--)
      a[idx++] = i;
  delete[] range;
}



int a[maxn],n;
int main()
{
  cin >> n;
  for(int i = 1; i <= n; i++)
     cin >> a[i];
  //插入排序
  //Straight_Insert_Sort(a,n);
  //Binary_Insert_Sort(a,n);
  //Shell_Sort(a,n);
  //交換排序
  //Bubble_Sort_A(a,n);
  //Bubble_Sort_B(a,n);
  //Bubble_Sort_C(a,n);
  //Quick_Sort(a,1,n);
  //選擇排序
  //Selec_Sort(a,n);
  //Heap_Sort(a,n);
  //歸併排序
  //Merge_Sort(a,1,n);
  //基數排序
  //Radix_Sort(a,n);
  //計數排序
  //Count_Sort(a,n);
  //桶排序
  //Bucket_Sort(a,n);
  for(int i = 1; i <= n; i++)
     cout << a[i] << " ";
  cout << endl;
  return 0;
}

 

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