c++實現計數排序

代碼說明

代碼是我親自碼的,調試通過的,代碼中有算法思想和詳細的註釋,一目瞭然。

項目已經上傳到我的github:https://github.com/yisun03/sort

項目中近期會陸續更新十種排序算法的c++實現代碼以及其思想。

術語說明

1、穩定排序:如果 a 原本在 b 的前面,且 a == b,排序之後 a 仍然在 b 的前面,則爲穩定排序。

2、非穩定排序:如果 a 原本在 b 的前面,且 a == b,排序之後 a 可能不在 b 的前面,則爲非穩定排序。

3、原地排序:原地排序指在排序過程中不申請多餘的存儲空間,只利用原來存儲待排數據的存儲空間進行比較和交換的數據排序。

4、非原地排序:需要利用額外的數組來輔助排序。

5、時間複雜度:一個算法執行所消耗的時間。

6、空間複雜度:運行完一個算法所需的內存大小。

性能分析

時間複雜度:代碼中1,2,4要遍歷原數組,令原數組長度爲n,則計算量爲3*n;

                      代碼中3需要便利統計容器,最大值和最小值差值爲d,所以統計容器長度爲d,計算量爲d,

                      所以時間複雜度爲O(3*n + d),去掉係數爲O(n+d)。

注意:很多人都會說計數排序的時間複雜度爲O(n),說性能優於快速排序,但其實並不是,因爲差值d一般都不是一個小的數,是不容忽視的,這就是爲什麼說計數排序時間複雜度優於快速排序但快速排序的應用廣的一部分原因。另一部分原因是因爲計數排序的缺陷,它只適用於整數序列,而且差值d太大的話空間複雜比較差,該缺陷有一個彌補的排序方法,那就是桶排序,桶排序見我的另一篇blog:  (更新中)

空間複雜度:O(d)

穩定的非原地排序
 

void sort::sort_count(std::vector<int> &data)
  {
    // 思想:
    // 計數排序的思想比較新穎,不再是基於元素之間的比較,而是將待排序序列的元素當作容器的索引,記錄索引出現的次數;
    // 比如臨時容器的a[i] = n,表示元素i出現n次;
    // 記錄玩出現次數之後,將臨時容器從小到大將元素彙總起來,則變爲有序;
    // 我的方法是有所改進,臨時容器記錄的不是元素出現的次數,而是記錄小於等於該元素的元素個數;
    // 這樣做的優點是可以保證穩定排序.
    // 計數排序的缺點:只能對整數序列進行排序,而且不適合最大元素和最小元素差得很大的情況(爲什麼呢).

    // 1.遍歷待排序數組,獲取最大和最小元素,並求得插值d.
    int max = data.at(0);
    int min = data.at(0);
    int length = static_cast<int>(data.size());
    for(int i = 1; i < length; i++)
    {
      if(data.at(i) > max)
      {
        max = data.at(i);
      }
      if(data.at(i) < min)
      {
        min = data.at(i);
      }
    }
    int d = max - min;
    // 2.創建統計容器,並遍歷待排序序列進行統計元素出現的次數.
    // 容器元素默認值爲0.
    std::vector<int> count_data;
    count_data.resize(d+1);
    for(int i = 0; i < length; i++)
    {
      // min值作爲一個偏移量的角色.
      ++count_data.at(data.at(i) - min);
    }
    // 3.改進以實現穩定排序,對統計容器做變形,統計容器元素存的不再是待排序元素出現的次數,
    // 而是記錄小於等於該索引的元素個數.
    int sum = 0;
    for(auto &value : count_data)
    {
      sum += value;
      value = sum;
    }
    // 4.倒序遍歷原始待排序序列,從統計容器中找到正確位置輸出到結果容器,
    // 如果沒有第3步,那麼就是簡單的輸出,是非穩定的.
    std::vector<int> sorted_data;
    sorted_data.resize(data.size());
    // 注意:此處的i的類型不能用auto來排段,因爲size()返回的類型爲unsigned long,又因爲是i--,
    // 所以如果是用auto的活,這個將是一個死循環,而且基本上會造成std::out_of_range錯誤.
    for(int i = static_cast<int>(data.size() - 1); i >= 0; i--)
    {
      // 代碼有點繞:
      // data.at(i)-min是當前元素與最小值的差值,
      // 以差值作爲count_data的索引值,則count_data.at(data.at(i)-min)代表小於等於當前元素data.at(i)的個數,
      // 所以count_data(data.at(i)-min)-1表示當前元素data.at(i)的升序序號.
      // 將當前元素data.at(i)放入sorted_data的正確的位置上.
      sorted_data.at(count_data.at(data.at(i) - min) - 1) = data.at(i);
      // 隨後更新統計容器的元素值,這和倒序遍歷是實現穩定排序的關鍵.
      --count_data.at(data.at(i)-min);
    }
    // 5.最後將已排序容器賦給原始序列,排序結束.
    data = sorted_data;
  }

 

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