全面分析插入排序的三種插入方式

豐千郎發表於2021-12-03

何謂‘插入排序’? 其概念如是說:每次將一個待排序的記錄,按其關鍵字大小插入到前面已經排序好的序列中,直到全部記錄插入完成為止。

概念的東西總是有些抽象,也可稱其為基本思想。上述插入排序的概念同樣也可說是插入排序的基本思想。抽象的東西理解起來總是有些困難,因此這裡需要的是將抽象的概念具體化。

我們不妨將其轉換成整隊問題:開始會有兩隊,其中一隊是按從低到高的順序排列的,將其命名為A隊。另一隊是無序的,將其命名為B隊。如下圖:

 

現在把B隊的第一個人(不妨稱其為jack)放到A隊中,並且保持A隊依然是有序的,為了保持A隊依然有序就需要在A隊中找一個適當的位置給jack,這個位置前面的人要比jack矮,後面的人要比jack高。現在就可以讓jack站到這個位置上面,此時A中依然有序。

 

然後再把B隊的第二個人(稱其為tom)放到A隊,方式和jack相同,要保證A隊依然有序。

 

依次類推直到B隊的人全部站到A隊當中。到此為止,兩隊合成了一隊,而且這一隊是有序的。

 

看到這對插入排序應該有一個比較清晰的認識了。但是這裡還存在著一個疑問,排序問題是對一個佇列進行排序,何來的兩隊呢。我們不妨再來轉換一下,起初的時候A、B兩隊站在同一列,並且A隊整體在B隊前面,並且A隊是一個人。對於一個人的佇列肯定是有序的。

 


 

然後再向程式碼方面靠近一下,不妨將A、B兩隊對映成陣列。有這樣一個陣列

 

其中 3 就表示 剛開始的A隊 ,5、2…. 表示剛開始的B隊。而5 就是 Jack, 2 就是Tom。

我當時學習插入排序的時候就是按照這個思路來理解的。到這裡我對插入排序的思想基本上已經完全掌握了。

思想的東西轉換成程式碼,不同的方式也會產生不同的‘派系’。好比《春秋經》讀起來總是有些難懂,這時就有人出來為春秋作傳,不同的人作出來的傳也是不同的,有《左傳》,有《公羊傳》,有《穀梁傳》 。 雖然有所不同,但是整體都是傳承的《春秋》的思想 。

現在回到我們的插入排序上來,既然思想的東西都已經明瞭了,無非就是實現方式的差別。關鍵的地方還是在於A隊,當在A隊為B隊的人查詢適當位置的時候,查詢的方式有很多種。
    1、每次開始從頭遍歷查詢位置 稱其為 直接插入排序
    2、用二分法查詢位置 稱其為 二分插入排序/折半插入排序

以上兩種方式 都是在一個佇列中查詢和移動元素,主要時間花費在查詢和移動兩個方面。
還有第三種方式就是藉助額外的佇列,來做一個對映,這樣可以省去了移動所花費的時間。
就是用空間換時間的做法,這種方式被稱為:
    3、表插入排序

下面我們分別看一下三種方式的實現程式碼

一、直接插入排序

實現步驟:

  1. 將第一個待排序的序列的第一個元素看做一個有序序列,把第二個元素到最後一個元素當成是未排序序列。
  2. 從頭到尾一次掃描未排序的序列,將掃描到的每個元素插入有序序列的適當位置(在這裡需要注意一個問題,如果在有序序列中有一個和待插入的元素相等,則將待插入的元素查到此元素的後面,這樣方式的插入排序是穩定的。如果插入到此元素的前面,那麼此種方式的插入排序是不穩定的
$arr1 = array(
15,77,23,43,90,87,68,32,11,22,33,99,88,66,44,113,
224,765,980,159,456,7,998,451,96,0,673,82,91,100
);
for($i=1;$i<count($arr1);$i++){
    $p = $arr1[$i];
    for($j=0;$j<$i;$j++){
        if($arr1[$j]>$p){
            break;
        }
    }
    for($k=$i-1;$k>=$j;$k--){
        $arr1[$k+1] = $arr1[$k];
    }
    $arr1[$j] = $p;
}

 

二、 折半插入排序

折半插入排序演算法步驟

  1. 將第一個待排序的序列的第一個元素看做一個有序序列,把第二個元素到最後一個元素當成是未排序序列。
  2. 從頭到尾依次掃描未排序的序列,將掃描到的每個元素插入有序序列的適當位置。折半插入排序根據二分查詢法在有序序列中查詢合適的位置將還未排序的元素插入。(在這裡需要注意一個問題,如果在有序序列中有一個和待插入的元素相等,則將待插入的元素查到此元素的後面,這樣方式的插入排序是穩定的。如果插入到此元素的前面,那麼此種方式的插入排序是不穩定的)
$arr1 = array(15,77,23,43,90,87,68,32,11,22,33,99,88,66,44,113,224,765
,980,159,456,7,998,451,96,0,673,82,91,100);
for($i=1;$i<count($arr);$i++){
    if($arr[$i]<$arr[$i-1]){
        //使用二分查詢法查詢適當的位置
        $low = 0;
        $high = $i-1;
        $pos = floor(($low+$high)/2);
        $key = $arr[$i];
        while($low<$high){
            if($arr[$pos]>$key){
                $high = $pos-1;
            }elseif($arr[$pos]<=$key){
                $low = $pos+1;
            }
            $pos = floor(($low+$high)/2);
        }
        //二分查詢法結束
        if($arr[$pos]>$arr[$i]){
            $pos = $pos-1;
        }
        for($j=$i-1;$j>$pos;$j--){
            $arr[$j+1]=$arr[$j];
        }
        $arr[$j+1] = $key;
    }
}

 

三、 表插入排序

表插入排序,顧名思義,藉助一個索引表對原表進行插入排序,這樣做的好處就是省去了對原來表中元素的移動過程。當然單一的整數陣列(僅作為試驗用)移動元素也是挺方便的,但是對於結構有些複雜的表來說,要想移動表中的元素那可真真不是一件容易的事情了。舉個例子(以下PHP中的二維陣列)

$link = array();  //連結串列
 
   $link[0]=array('next'=>1);//初始化連結串列  $link第一個元素僅僅作為頭部
 $link[1]=array('next'=>0); //將第一個元素放入$link
/*
  * 開始遍歷陣列 從第二個元素開始
  */
 for($i=2;$i<=count($arr);$i++){
     $p = $arr[$i]; //儲存當前待排序的元素
     $index =0;
     $next = 1;  //從開始位置查詢連結串列
     while($next!=0){
         if($arr[$next]['age']<$p['age']){
             $index = $next;
             $next = $link[$next]['next'];
         }
         else break;
     }
     if($next == 0){
         $link[$i]['next'] = 0;
         $link[$index]['next'] = $i;
     }else{
         $link[$i]['next']=$next;
         $link[$index]['next']=$i;
     }
 }

 

以上是三種插入排序的簡單步驟及實現程式碼,詳細的介紹可以參考下面三篇文章

 

相關文章