數據結構—各類‘排序算法’實現(下)

       在上一篇博客中,主要是實現各種的排序算法,並針對一些算法進行了優化的處理,下面主要討論一下非比較排序的算法(計數排序、基數排序),同時並對各種排序算法的性能、時間複雜度、空間複雜度、優缺點、以及適用場景做總結分析。


1.計數排序

         主要思想:主要是需要統計次數,使用直接定址法,統計最大數和最小數,開闢兩個數相差的空間大小,對於重複數據,使用count用來計數,時間複雜度O(N+範圍個數),空間複雜度O(範圍個數)計數排序適合於數據較爲密集的情況,當數據密集且沒有重複的數據,可以直接使用‘位圖’,更能夠省下空間


void CountSort(int* a, size_t size)
{
     assert(a);
     int max = a[0];
     int min = a[0];
     int count = 0;
 
     for (size_t i = 0; i < size; ++i)      //尋找數組中的最大數和最小數
     {
          if (a[i] < min)
          {
               min = a[i];
          }
          if (a[i] > max)
          {
               max = a[i];
          }
     }
     
     int* tmp = new int[max - min + 1];    //開闢存儲空間,並初始化
     memset(tmp, 0, sizeof(int)*(max - min + 1));
     for (size_t i = 0; i < max - min + 1; ++i)   //直接定址法
     {
          int num = a[i] - min;
          tmp[num]++;
     }
     for (size_t i = 0; i < size;)    //將排序好的順序寫入a數組中
     {
          for (size_t j = 0; j < max - min + 1; ++j)
          {
               count = tmp[j];
               while (count--)    //對於重複數據需要多次進行寫入
               {
                    a[i] = j + min;
                    i++;
               }
          } 
     }
     delete[] tmp;
}


2.基數排序

        主要思想:和‘快速轉置’的思想相像,開闢兩個數組count和start,count用來統計個位上分別爲0~9的數據個數,start用來統計數據的開始位置(起始位置爲0,下一位的數據開始的位置=上一個數據的開始位置+上一位總的數據個數),另開闢size大小的空間來存放每次排序,下面是低位基數排序,從個位開始排序,然後排序十位,進而百位,直到排到最大數據的最高位,排序結束。


int GetMaxRadix(int* a, size_t size)    //尋找最大數據的位數
{
     int index = 1;   //數據最小有一位
     int max = 10;
     for (size_t i = 0; i < size; ++i)
     {
          while (a[i] >= max)      //數據大於1位
          {
               index++;
               max = max * 10;
          }
     }
     return index;
}

void LSDSort(int* a, size_t size)
{
     assert(a);
     int index = GetMaxRadix(a, size);   //求最大數據的位數
     int count[10] = { 0 };    //記錄數據出現的次數
     int start[10] = { 0 };   //記錄數據的開始位置
     int radex = 1;
     int* bucket = new int[size];   
     
     for (int k = 1; k <= index; ++k)    //從各位到最高位排序
     {
          memset(count, 0, sizeof(int)* 10);    //每次排序之前需將count置0
          //計數(各位分別爲0~9的數據個數)
          for (size_t i = 0; i < size; ++i)
          {
               int num = (a[i] / radex) % 10;     //取個位
               count[num]++;
          }
          
          //記錄數據開始的位置
          start[0] = 0;
          int j = 1;
          while (j < 10)
          {
               start[j] = start[j - 1] + count[j - 1];
               j++;
          }
          for (size_t i = 0; i < size; ++i)   //將數據按順序放入bucket中
          {
               int num = (a[i] / radex) % 10;
               bucket[start[num]++] = a[i];
          }
          radex *= 10;
          memcpy(a, bucket, sizeof(int)* size);
     }    
     delete[] bucket;
}


3.排序算法總結

(1)各種排序算法的性能分析

650) this.width=650;" title="無標題.png" src="http://s3.51cto.com/wyfs02/M01/80/76/wKiom1dCiIHyWA01AABinr6tUkk426.png" alt="wKiom1dCiIHyWA01AABinr6tUkk426.png" />

 其中:r爲數據範圍的個數 


穩定性分析:

        穩定性:指的是需要排序的數據之中如果有相同的數據元素,在排序前、後的相對位置是不變的,即就是當排序{1,3,5,7,2,5,6},通過排序後‘5’'5'之前,而不是相互交換了。

         在介紹的這幾種排序之中插入排序、冒泡排序、歸併排序、計數排序是穩定的。快速排序、希爾排序、選擇排序、堆排序是不穩定的


空間複雜度:

        排序算法中,快速排序(需要進行遞歸)、歸併排序、計數排序、基數排序都是需要額外的空間進行排序。其餘的排序算法就不需要藉助任何的空間。


時間複雜度:

 O(N^2):

        插入排序、冒泡排序、選擇排序都是空間複雜度爲O(N^2),排序效率基本上都是比較低,選擇排序是最不好的,因爲在最有的情況下,也需要N^2的時間複雜度,相對來說,插入和冒泡能好一點,在優化的情況下,能夠減少排序的時間。但是當數據量較大時,冒泡的時間代價會更高。


O(N*lgN):

       平均性能爲O(N*lgN)的算法:快速排序、歸併排序、堆排序算法,快速排序經過各種的優化算法(三數取中法、區間較小時利用直接插入算法,【上篇博客中詳細的介紹了快速排序的優化】)已經相對來說效率提高了很多。

       歸併排序又稱爲外排序,外排序其實就是指能夠對內存之外(磁盤中)數據進行排序,對於大數據的文件,不能夠直接加載到內存中進行排序,我們可以採取將文件劃分成小文件,將小的文件加載到內存中進行排序,然後將排好序的數據進行重寫,將兩個有序的數據文件在重新排序,就能夠排好大數據文件。這個讀者可以下去進行試驗,這裏就不做詳細的解釋。

       堆排序的效率雖然還是挺高的,但是堆排序有個致命的缺點,就是隻能夠對數組進行排序,因爲需要通過數組的下標來確定數據在堆中的具體位置。所以堆排序只能對數組進行排序



本文出自 “無心的執着” 博客,請務必保留此出處http://10740590.blog.51cto.com/10730590/1782091

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