1. 程式人生 > >淺談C++之冒泡排序、希爾排序、快速排序、插入排序、堆排序、基數排序性能對比分析(好戲在後面,有圖有真相)

淺談C++之冒泡排序、希爾排序、快速排序、插入排序、堆排序、基數排序性能對比分析(好戲在後面,有圖有真相)

棧溢出 分享圖片 隨機數 函數 大根堆 oschina 共同學習 時間復雜度 還原

由於沒考慮到一些情況,對以上一些算法做了改進和對比!以及昨晚把希爾排序寫錯而誤以為其效率高過快速排序的糗事,今天一一做了更正和說明,如果你絕得本隨筆不是很妥可以嘗試看看這http://www.cnblogs.com/maxiaofang/p/3382927.html,有錯誤或不妥歡迎指正!!共同學習,共同進步!O(∩_∩)O哈哈~

  推薦一段博友分享的排序視頻很藝術、很形象、很生動哦(http://www.oschina.net/question/561584_65522)

  最近一段時間去武漢參加了N多筆試,在幾次試題中都出現了排序。偏偏出現了我沒怎麽看的插入排序,弄得我好是糾結。趁回學校的機會把這幾個不是很復雜的排序重新復習了一下,借此比較了一下他們的效率。讓我有點以外的是在數據量達到1W~10W之間,希爾排序竟然比快速排序效率還要高。貼上完整代碼!

冒泡排序

技術分享圖片
 1 //冒泡排序
 2 //////////////////////////////////////////////////////////////////////////
 3 void BubleSort(int a[],int n)
 4 {
 5     int temp;
 6     bool flag=false;
 7     for (int i=0;i<n;i++)
 8     {
 9         flag=true;
10         for (int j=0;j<n-i-1;j++)
11         {
12             if(a[j]>a[j+1])
13             {
14                 temp=a[j];
15                 a[j]=a[j+1];
16                 a[j+1]=temp;
17                 flag=false;
18             }
19         }
20         if(flag) break;
21     }
22 }
技術分享圖片

冒泡排序的時間復雜度為O(n²),在數據比較小的情況下各個算法效率差不多。

希爾排序:

  以前竟然沒有發現,希爾排序如此短小精悍的代碼。其效率很多時候並不輸給快速排序其時間復雜度為O(nlogn)。

技術分享圖片
 1 void ShellSort(int array[],int length)
 2 
 3 {
 4     
 5     int d = length/2;   //設置希爾排序的增量
 6     int i ;
 7     int j;
 8     int temp;
 9     while(d>=1)    
10     {
11         for(i=d;i<length;i++)    
12         {    
13             temp=array[i];
14             j=i-d;
15             while(j>=0 && array[j]>temp)    
16             {    
17                 array[j+d]=array[j];    
18                 j=j-d;    
19             }    
20             array[j+d] = temp;    
21         }
22         //Display(array,10);    
23      d= d/2;    //縮小增量    
24     }    
25 }
技術分享圖片

快速排序:

技術分享圖片
 1 //快速排序
 2 ///////////////////////////////////////
 3 void Swap(int &a,int &b)
 4 {
 5     int temp;
 6     temp=a;
 7     a=b;
 8     b=temp;
 9 }
10 
11 int Partition(int a[],int p,int r)
12 {
13     int i=p;
14     int j=r+1;
15     int x=a[p];
16     while (true)
17     {
18         while(a[++i]<x&&i<r);
19         while(a[--j]>x);
20         if (i>=j)break;
21         Swap(a[j],a[i]);
22 
23     }
24     a[p]=a[j];
25     a[j]=x;
26     return j;
27 }
28 
29 void QuickSort(int a[],int p,int r)
30 {
31     if (p<r)
32     {
33         int q=Partition(a,p,r);
34         QuickSort(a,p,q-1);
35         QuickSort(a,q+1,r);
36     }
37 }
技術分享圖片

  正如其名快速排序,其效率也是比較高的,時間復雜度為O(nlogn)。其算法思想是每次確定一個基準值的位置,也就是函數int Partition(int a[],int p,int r)的作用。然後通過遞歸不斷地確定基準值兩邊的子數組的基準值的位置,直到數組變得有序。難點還是遞歸的理解!

  插入排序:

  

技術分享圖片
 1 //插入排序
 2 //////////////////////////////////////////////////////////////////
 3 void Insert(int *a,int n) 
 4 {
 5     int i=n-1;
 6     int key=a[n];//需要插入的元素
 7     while ((i>=0)&&(key<a[i]))
 8     {
 9         a[i+1]=a[i];    //比key大的元素往後一個位置,空出插入key的位置
10         i--;
11     }
12     a[i+1]=key;//找到位置插入元素
13     return;
14 }
15 
16 //由於遞歸的原因數太大了棧可能會溢出
17 void InsertionSort(int *a,int n)
18 {
19     if (n>0)
20     {
21         InsertionSort(a,n-1);
22         Insert(a,n);
23     }
24     else return;
25 }
技術分享圖片

  算法效率和冒泡排序相差無幾,時間復雜度為O(n²)。這裏要註意的問題是由於不斷地遞歸,棧的不斷開辟如果數據太大可能會導致棧溢出而不能得到結果。

  堆排序:

技術分享圖片
 1 //堆排序
 2 ////////////////////////////////////////////////////////////////////////////
 3 int Parent(int i)
 4 {
 5     return i/2;
 6 }
 7 int Left(int i)
 8 {
 9     return 2*i;
10 }
11 int Right(int i)
12 {
13     return 2*i+1;
14 }
15 
16 //把以第i個節點給子樹的根的子樹調整為堆
17 void MaxHeap(int *a,int i,int length)
18 {
19     int L=Left(i);
20     int R=Right(i);
21     int temp;
22     int largest;                  //記錄子樹最大值的下表,值可能為根節點下標、左子樹下表、右子樹下標
23     if (L<=length&&a[L-1]>a[i-1]) //length是遞歸返回的條件
24     {
25         largest=L;
26     }
27     else largest=i;
28     if (R<=length&&a[R-1]>a[largest-1]) //length是遞歸返回的條件
29         largest=R;
30     if (largest!=i)
31     {
32         temp=a[i-1];
33         a[i-1]=a[largest-1];
34         a[largest-1]=temp;
35         MaxHeap(a,largest,length);
36     }
37 }
38 
39 void BuildMaxHeap(int *a,int length)
40 {
41 
42     for (int i=length/2;i>=1;i--)
43         MaxHeap(a,i,length);
44 }
45 
46 void HeapSort(int *a,int length)
47 {
48     BuildMaxHeap(a,length);
49     for (int i=length;i>0;i--)
50     {
51         int temp;
52         temp=a[i-1];
53         a[i-1]=a[0];
54         a[0]=temp;
55         length-=1;
56         MaxHeap(a,1,length);
57     }
58 }
技術分享圖片

  通過使用大根堆來排序,排序過程中主要的動作就是堆的調整。每次把堆的根節點存入到堆的後面,然後把最後一個節點交換到根節點的位置,然後又調整為新的堆。這樣不斷重復這個步驟就能把把一個數組排列的有序,時間復雜度為O(nlogn)。

  最後一種是比較特別的基數排序(屬於分配式排序,前幾種屬於比較性排序)又稱“桶子法”:

  基本思想是通過鍵值的部分信息分配到某些桶中,藉此達到排序的作用,基數排序屬於穩定的排序,其時間復雜度為O(nlog(r)m),r為所采取的的基數,m為堆的個數,在某些情況下基數排序法的效率比其他比較性排序效率要高。

  

技術分享圖片
 1 //基數排序
 2 /////////////////////////////////////////////////
 3 int GetMaxTimes(int *a,int n)
 4 {
 5     int max=a[0];
 6     int count=0;
 7     for (int i=1;i<n;i++)
 8     {
 9         if(a[i]>max)
10             max=a[i];
11     }
12     while(max)
13     {
14         max=max/10;
15         count++;
16     }
17     return count;
18 }
19 
20 void InitialArray(int *a,int n)
21 {
22     for (int i=0;i<n;i++)
23         a[i]=0;
24 }
25 
26 // void InitialArray1(int a[][],int m,int n)
27 // {
28 //     for (int i=0;i<m;i++)
29 //         for (int j=0;j<n;j++)
30 //             a[i][j]=0;
31 // }
32 
33 void RadixSort(int *a,int n)
34 {
35     int buckets[10][10000]={0};
36     int times=GetMaxTimes(a,n);
37     int index,temp;
38     int record[10]={0};
39     for (int i=0;i<times;i++)
40     {
41         int count=0;
42         temp=pow(10,i);//index=(a[j]/temp)%10;用來從低位到高位分離
43         for (int j=0;j<n;j++)
44         {
45             index=(a[j]/temp)%10;
46             buckets[index][record[index]++]=a[j];
47         }
48         //把桶中的數據按順序還原到原數組中
49         for(int k=0;k<10;k++)
50             for (int m=0;m<100000;m++)
51             {
52                 if(buckets[k][m]==0)break;
53                 else
54                 {
55                     a[count++]=buckets[k][m];
56                     //cout<<buckets[k][m]<<" ";
57                 }
58             }
59             //重新初始化桶,不然前後兩次排序之間會有影響
60             //buckets[10][10000]={0};
61             //record[10]={0};
62             //InitialArray1(buckets,10,10000);
63             for (k=0;k<10;k++)
64                 for (int m=0;m<100000;m++)
65                 {
66                     if(buckets[k][m]==0)break;
67                     else buckets[k][m]=0;
68                 }
69             InitialArray(record,10);
70     }
71 }
技術分享圖片

  在這裏需要註意的是由於局部變量桶過大可能會導致棧溢出而得不帶結果,比如桶為int buckets[10][100000]={0};大小=(10*100000*4)/(1024*1024)=3.814M,如果棧的大小只有1M~3M的話就會溢出,就得不到結果,當然可以把這個局部變量改成全局變量。

  下面是從數據量10~80000排序的實驗結果,首先聲明一點。每個排序算法的數據量是相同的而具體數據都是隨機產生的,也就是每個排序一組不同的隨機數據。這可能對實驗結果有所影響。當然我這只是沒事好奇搞著玩玩,結果僅供參考。大家有興趣的可以自己試試!

數據量為10:

技術分享圖片

數據量為100:

技術分享圖片

數據量為1000:

技術分享圖片

數據量為10000:

技術分享圖片

數據量為15000:

技術分享圖片

數據量為20000:

技術分享圖片

數據量為50000:

技術分享圖片

數據量為90000:

技術分享圖片

數據量為80000:

技術分享圖片

  越來越興奮了:

技術分享圖片

技術分享圖片

接下來想測一測10億是神馬情況程序直接掛了,然後測一測5億然後就死機了,然後就木有然後了,我寫了一半的博客!!!!!~~o(>_<)o ~~!!!!!~~o(>_<)o ~~!!!!!~~o(>_<)o ~~

後面測了一下5億!本來錄了一段小視頻的,但是上傳不了。這裏就說出答案吧:5億數據時,快速排序也掛了,只有希爾排序一直在健壯的運行,運行時間大概為120s左右。

技術分享圖片

大概分析了一下數據所占的內存:

首先5億個數據占多少內存?

(50000000*4)/(pow(1024,3))=1.86G

我的電腦內存為3G左右,除去操作系統和軟件大約占了20%3G=0.6G。

3-0.6-1.86=0.54G剩余

0.54*pow(1024,3)/4=144955146剩余內存還可以計算1億多個數據,

所以我的電腦一共能同時排序644955146個數據。這就是為什麽排序10億數據時程序崩潰的原因,因為沒有這麽多內存分配給程序使用。

然而我實際測了一下實際上達不到6億,5.5億就崩潰了,原因有待後續考察!

由於沒考慮到一些情況,對以上一些算法做了改進和對比!以及昨晚把希爾排序寫錯而誤以為其效率高過快速排序的糗事,今天一一做了更正和說明,如果你絕得本隨筆不是很妥可以嘗試看看這篇http://www.cnblogs.com/maxiaofang/p/3382927.html,有錯誤或不妥歡迎指正!!共同學習,共同進步!O(∩_∩)O哈哈~

淺談C++之冒泡排序、希爾排序、快速排序、插入排序、堆排序、基數排序性能對比分析(好戲在後面,有圖有真相)