數據結構——常見的十種排序算法


一、常見的十種排序算法:

  冒泡排序、選擇排序、插入排序、歸並排序、快速排序、希爾排序、堆排序、計數排序、桶排序、基數排序
1.【知識框架】
 
補充:
內部排序:整個排序過程完全在內存中進行。
外部排序:由於待排序記錄數據量太大,內存無法容納全部數據,需要借助外部存儲。
 
二、排序方法
 
插入排序
•直接插入排序
1.算法思想
    從待排序的第二個元素開始,向下掃描列表,比較這個目標值target與arr[i-1]、arr[i-2]的大小,依次類推。如果target的值小於或等於每一個元素值,那么每個元素都會向右滑動一個位置,一旦確定了正確位置j,目標值target(即原始的arr[i])就會被復制到這個位置。( 例如:整理橋牌時,我們會將每一張牌插入到一個已經有序的序列的適當位置。為了給要插入的元素騰出空間,需要我們將已經有序的序列元素都向右移動一位)
2.算法實現

 

3.插入排序偽代碼

 1 void InsertSort (ElemType A[], int n)
 2 {
 3    int i,j;
 4    for(i=2;i<=n;i++)
 5         if(A[i].key<A[i-1].key)
 6     {
 7         A[0]=A[i];
 8         for(j=i-1;A[0].key<A[j].key;--j)
 9                 A[j+i]=A[j];
10         A[j+i]=A[0];
11     }
12 }

4.穩定性

  由於每次插入元素時總是從后往前先比較在移動,所以不會出現相同元素相對位置,發生變化的情況即直接插入排序是一個穩定的排序方法

5.時間復雜度:O(n²)

 

 •希爾排序(縮小增量排序)

1.算法提出:

 為解決插入排序每次只能將數據移動一位,在數組較大且基本無序的情況下性能出現的惡化所以引入其算法。
2.算法思想: 
 先取一個小於n的步長d1把表中全部記錄分成d1個組,所有距離為d1的倍數的記錄放在同一個組中,在各組中進行直接插入排序:然后取第二個步長d2<d1,重復上       述過程,直到所取到的d1=1,即所有記錄已放在同一組中,再進行直接插入排序,由於此時已經具有較好的局部有序性,故可以很快得到最終結果。到目前為         止,尚未求得一個最好的增量序列,希爾提出的方法是d1=n/2,di+1=⌈di/2⌉ , 並且最后一個增量等於 1。
3.算法實現:

補充:操作原理時間復雜度與選取的增量序列有關且所取增量序列的函數介於O(N*logN)和O(n²)之間增量序列有很多種取法,但是使增量序列中的值沒有除1之外的公因子,並且增量序列中最后一個值必須為1。

4.希爾排序偽代碼

 1 void ShellSort (ElemType A[],int n){
 2 //對順序表作希爾插入排序,基本算法和直接插入排序相比,做了以下修改:
 3 //1.前后記錄位置的增量是dk,不是1
 4 //2.r[0]只是暫時存儲單元,不是哨兵,當j<=0時,插入位置已到
 5       for(dk=n/2;dk>=1,dk=dk/2)                                   //步長變化
 6            for(i=dk+1;i<=n;++i) 
 7                  if(A[i].key<A[i-dk].key){                        //需將A[i]插入有序增量子表
 8                      A[0]=A[i];                                                       
 9                      for(j=i-dk;j>0&&A[0].key<A[j].key;j-=dk) 
10                           A[j+dk]=A[j];                           //記錄后移,查找插入位置
11                      A[j+dk]=A[0];                                //插入
12                }//if
13   }

 5.穩定性

   當相同關鍵字的記錄被划分到不同的子表時,可能會改變它們之間的相對次序,因此,希爾排序是一種不穩定的排序方法。例如,表L=[3,2,2].經過一趟排序后,L=[2,2,3],最終排序序列也是L=[2,2,3],顯然2與2的相對次序已經發生了變化。

6.時間復雜度:O(N*logN)

 

選擇排序

•簡單選擇排序

1.算法思想

Step1:將待排序數組分為有序和無序兩組(初始情況下有序組為空)。

Step2:從左向右掃描無序組,找出最小的元素,將其放置在無序組的第一個位置。至此有序組++,無序組--;

Step3:重復Step2,直至無序組只剩下一個元素。

2.算法實現

3.選擇排序偽代碼

 1 void ShellSort (ElemType A[],int n){
 2 //對表A作簡單的選擇排序,A[]從0開始放元素
 3       for(i=0;i<=n-1;i++){                                //一共進行n-1趟
 4           min=i;                                          //記錄最小元素位置
 5               for(j=i+1;j<n;j++)                          //在A[i...n-1]中選擇最小的元素                                    
 6                      if(A[j]<A[min])
 7                            min=j;                         //更新最小元素位置
 8 if(min!=i)  swap(A[i],A[min]);                            //在第n個元素交換
 9                         
10                }
11 }

4.穩定性

  選擇排序的時間復雜度為O(n²),由於每次選擇僅考慮某一位置上的數據情況,可能會破壞之前數據的相對位置,因此它是一種不穩定的排序方法。 例如:序列  [9,9,1]第一次就將第一個[9]與[1]交換,導致第一個9挪動到第二個9后面。

5.時間復雜度: O(n²)

補充:簡單選擇排序的比較次數與序列的初始排序無關。假設待排序的序列有 n個元素,選擇排序的賦值操作介於0和3(n - 1次之間; 則比較次數永遠都是n(n-1)/2; 而移動次數(即:交換操作)與序列的初始排序有關,介於 0 和 (n - 1) 次之間。當序列正序時,移動次數最少,為 0。當序列反序時,移動次數最多,為n - 1 次;逆序交換n/2次。選擇排序的移動次數比冒泡排序少多了,由於交換所需CPU時間比 比較所需的CPU時間多,n值較小時,選擇排序比冒泡排序快。

•堆排序

1.引入概念
 
堆是一棵順序存儲的完全二叉樹。其中每個結點的關鍵字都不大於其孩子結點的關鍵字,這樣的堆稱為小根堆。
其中每個結點的關鍵字都不小於其孩子結點的關鍵字,這樣的堆稱為大根堆。
舉例來說,對於n個元素的序列{R0, R1, ... , Rn}當且僅當滿足下列關系之一時,稱之為堆: 

2.算法思想

    堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在數組的非降序排序中,需要使用的就是大根堆,因為根據大根堆的要求可知,最大的值一定在堆頂。
如圖:


3.算法實現(大頂堆)
注:若n為數組的個數,那么就從2/n開始,依次循環。
4.堆排序偽代碼
下面是建立大根堆的算法:
 1    void BuildMaxHeap (ElemType A[], int len) {
 2           for(int i=len/2;i>0;i--)  //從i=[n/2]~1,反復調整堆
 3               AdjustDown(A, i, len) ;
 4 
 5    }
 6     void AdjustDown (ElemType A[], int k, int len) {
 7                  //函數AdjustDown將元素k向下進行調整
 8             A[0]=A[k] ;                         //A[O]暫存
 9             for (i=2*k;i<=len;i*=2){      //沿 key較大的子結點向下篩選
10                   if (i<len&&A[i] <A[i+1])
11                         i++;
12                   if(A[0]>=A[i]) break;       // 篩選結束
13                   else{
14                         A[k]=A[i];                //將A[i]調整到雙親結點上
15                         k=i ;                        //修改k值,以便繼續向下篩選
16             }
17     }//for
18     A[k]=A[0];                                   //被篩選結點的值放入最終位置
19    }

下面是堆排序算法:

1                   void HeapSort (ElemType A[],int len) {
2                        BuildMaxHeap (A,len) ;          //初始建堆
3                        for(i=len;i>1;i--){              //n-1 趟的交換和建堆過程
4                        Swap (A[i],A[1]) ;                / /輸出堆頂元素(和堆底元素交換)
5                        AdjustDown (A, 1, i-1) ;}     //整理,把剩余的i-1個元素整理成堆
6                 }//for
7            }

下面是向上調整堆的算法:

 1                void AdjustUp (ElemType A[], int k) {
 2                 //參數k為向上調整的結點,也為堆的元素個數
 3                    A[0]=A[k] ;
 4                    int i=k/2;  //若結點值大於雙親結點, 則將雙親結點向下調,並繼續向上比較 
 5                    while (i>0&&A[i]<A[0]) {           //循環跳出條件
 6                           A[k]=A[i];                    //雙親結點下調 
 7                           k=i;
 8                           i=k/2;                           //繼續向上比較
 9             }//while
10             A[k]=A[0] ;                                  //復制到最終位置
11        }
5.穩定性
   堆排序是一種不穩定的排序方法。因為在堆的調整過程中,關鍵字進行比較和交換所走的是該結點到葉子結點的一條路徑,因此對於相同的關鍵字就可能出現排在后面的關鍵字被交換到前面來的。
6.時間復雜度O(N*logN)。

交換排序

•冒泡排序

1.算法思想

  冒泡排序是一種交換排序,它的實現原理是:兩兩比較相鄰的記錄值,如果反序則交換,直到沒有反序的記錄為准。對數組中的各數據,依次比較相鄰的兩元素的大小。如果前面的數據大於后面的數據,就交換這兩個數據。經過第一輪的多次比較排序后,變可把最小的數據排好。再用同樣的方法把剩下的數據逐個進行比較,最后便可按照從小到大的順序排好數組各數據的順序。

2.算法實現

3. 冒泡排序偽代碼 

 1 void BubbleSort (ElemType A[],int n){
 2 //用冒泡排序將序列A中的元素按從小到大排列
 3          for(i=0;i<n-1;i++){
 4               flag=false;                           //標示本趟冒泡是否發生交換標志
 5               for(j=n-1;j>i;j--)               //一趟冒泡過程
 6                    if(A[j-i].key>A[j].key){     //若為逆序
 7                          swap(A[j-1],A[j]);       //交換
 8                          flag=true;
 9                    }
10              if(flag==false)
11                   return;                            //本趟遍歷沒有發生交換,說明已經有序
12          }
13 }

4.穩定性

  冒泡排序是把小的元素往前調或者把大的元素往后調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。相同元素的前后順序並沒有改變,冒泡排序是一種穩定排序算法。

5.時間復雜度: O(n²)

補充:若文件的初始狀態是正序的,一趟掃描即可完成排序。所需的關鍵字比較次數C和記錄移動次數M均達到最小值:Cmin = N - 1, Mmin = 0。所以,冒泡排序最好時間復雜度為O(N)。若初始文件是反序的,需要進行 N -1 趟排序。每趟排序要進行 N - i 次關鍵字的比較(1 ≤ i ≤ N - 1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數均達到最大值:Cmax = N(N-1)/2 = O(N^2) Mmax = 3N(N-1)/2 = O(N^2)冒泡排序的最壞時間復雜度為O(N^2)。因此,冒泡排序的平均時間復雜度為O(N^2)。當數據越接近正序時,冒泡排序性能越好。

 

•快速排序

1.基本概念

  快速排序為應用最多的排序算法,因為快速二字而聞名。快速排序和歸並排序一樣,采用的都是分治思想。快速排序可以分為:單路快速排序,雙路快速排序,三路快速排序,他們區別在於選取幾個指針來對數組進行遍歷 。

1.算法思想

  快速排序是對冒泡排序的一種改進。其基本思想是基於分治法的:在待排序表L[`1...n]中任取一個元素 pivot作為基准,通過一趟排序表將待排序表划分為獨立的兩部分
L[1...k-1]和L[k+1...n],使得L[1...k-1]中所有元素小子pivot, L[k+1...n]中所有元素大於或等於pivot,則pivot放在了其最終位置L(k)上,這個過程稱作一趟快速排序。 而后分別遞歸地對兩個子表重復上述過程,直至每部分內只有一個元素或空為止,即所有元素放在了其最終位置上。

2.算法實現

注:后找小前找大

3. 快速排序偽代碼

  首先假設划分算法已知,記為Partition(),返回的是上述中的k,注意到L(k)已經在最終的位置,所以可以先對表進行划分,而后對兩個表調用同樣的排序操作。因此可以遞歸地調用快速排序算法進行排序,具體的程序結構如下:

1  void QuickSort (ElemType A[],int low,int high){
2 
3       if (low<high) {  //遞歸跳出的條件
4       //Partition()就是划分操作,將表low-high划分為滿足上述條件的兩個子表
5       int pivotpos-=Partition(A, low,high); //划分//依次對兩個子表進行遞歸排序 
6       QuickSort MB (A, low,pivotpos-1);
7       QuickSort (A, pivotpos+1,high) ;
8    }
9 }

   從上面的代碼也 不難看出快速排序算法的關鍵在於划分操作,同時快速排序算法的性能也主據要取決於划分操作的好壞。假設每次總是以當前表中第一個元素作為樞軸值 (基准)對表進行划分,則必須將表中比樞軸值大的元素向右移動,比樞軸值小的元素向左移動,使得一趟Partition()操作之后,表中的元素被樞軸值一分為二。
4.穩定性
  快速排序有兩個方向,左邊的i下標一直往右走,當a[i] <= a[center_index],其中center_index是中樞元素的數f組下標,一般取為數組第0個元素。而右邊的j下標一直往左走,當a[j] > a[center_index]。如果i和j都走不動了,i <= j, 交換a[i]和a[j],重復上面的過程,直到i>j。 交換a[j]和a[center_index],完成一趟快速排序。在中樞元素和a[j]交換的時候,很有可能把前面的元素的穩定性打亂,比如序列為 5 3 3 4 3 8 9 10 11, 現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂,所以快速排序是一個不穩定的排序算法,不穩定發生在中樞元素和a[j] 交換的時刻。
5.時間平均復雜度: O(nlog2n)。

基數排序(桶排序)
1.基本概念
  基數排序(Radix Sort)屬於分配式排序,又稱"桶子法"(Bucket Sort或Bin Sort),將要排序的元素分配到某些"桶"中,以達到排序的作用。基數排序屬於穩定的排序,其時間復雜度為nlog(r)m (其中r為的采取的基數,m為堆數),基數排序的效率有時候高於其它比較性排序。

2.算法思想

 1. 根據輸入建立適當個數的桶,每個桶可以存放某個范圍內的元素;
 2.將落在特定范圍內的所有元素放入對應的桶中;
 3.對每個非空的桶中元素進行排序,可以選擇通用的排序方法,比如插入、快排;
 4.按照划分的范圍順序,將桶中的元素依次取出。排序完成。
3. 算法實現
 數組: int arr[] = {278,109,63,930,589,184,505,269,8,83};

個位分配,個位收集,先進后出。

十位分配,十位收集,先進后出。

百位分配,百位收集,先進后出。

4.穩定性
  基數排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優 先級排序,最后的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,所以其是穩定的排序算法。

5.時間平均復雜度:O(d(n+r))

歸並排序(二路歸並排序)

1.基本概念
  歸並排序是通過“歸並”操作完成排序的,將兩個或者多個有序子表歸並成一個子表。歸並排序是“分治法”的一個非常典型的應用,同事它也是遞歸算法的一個好的實例。它將問題分成一些小的問題然后遞歸求解,而治就是把分階段的答案拼起來。
2.算法思想
  將一個大小為 n 的數組排序。歸並排序算法的排序步驟是:

  1.將所有的數字放入一個無序的堆。
  2.將堆分成兩部分,現在你有兩個無序的堆。
  3.持續將無序的堆拆分,直到無法再拆分為止,你將得到 n 個堆,每一個堆中有一個數字。
  4.現在開始將這些堆按照一定順序按對合並。每一次合並過程中,將堆中的數字放入有序的隊列。這一點很容易實現,因為每一個獨立的堆中的內容都是有序的。
3.算法實現

數組: int arr[] = {3,6,1,7,9,4,5,8,2}
二路歸並排序的過程如圖所示:

4.穩定性
  歸並排序的空間復雜度O(n)。另外,歸並排序中歸並的算法並不會將相同關鍵字的元素改變相對次序,所以歸並排序是穩定的。
5.時間平均復雜度:O(nlog2n)。

后續未完...

 



 

 

 

 

 

 

 

 

 

 


  




 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM