插入排序以及優化

codefly發表於2019-03-04

插入排序是一種非常重要的排序演算法,對於接近有序的陣列來說,使用插入排序進行排序可能要比使用歸併排序等O(NlogN)級別的排序演算法還要快,很多高階的演算法也都採用了插入排序的思想。這裡總結一下,插入排序的思想以及插入排序的優化方式。

記得我一開始學習插入的排序的時候,總是習慣這麼寫插入排序(用PHP來實現,用其他語言也是一樣):

<?php
$rand_arr = [6,5,1,7,3];
$array_count = count($rand_arr);
for($i=1;$i<$array_count;$i++){
    //每次取出陣列中的第i個元素與陣列中的index小於i的元素進行比較
    //如果需要比較的資料大於第i個元素,就進行資料的交換,將較大值放到第i的位置
    //從第0個元素開始向左進行運動,內部迴圈一次取出前i個元素中的某一個元素與第i個元素進行比較
    for($j=0;$j<$i;$j++){
        if($rand_arr[$j] > $rand_arr[$i]){
            $temp = $rand_arr[$j];
            $rand_arr[$j] = $rand_arr[$j+1];
            $rand_arr[$j+1] = $temp;
        }
    }
}
print_r($rand_arr);
?>複製程式碼

排序的順序如下:

開始階段: [ 6 5 1 7 3 ] : i =1,指向index=1的元素,也就是5,j=0,指向index=0的元素,也就是6

開始第一次外部迴圈: 6和5進行比較,因為5<6,所以進行資料交換的操作(6和5進行交換),得到一下的結果 [ 5 , 6 , 1 , 7 , 3 ],此時j+1 = 1, j=i,所以內部迴圈結束,開始第二輪外部迴圈。

開始第二輪外部迴圈: i=2,指向index=2的元素,也就是1,j=0,指向index=0的元素 ,也就是5。因為1小於5,所以進行交換,得到以下的結果[ 1 , 6 , 5 , 7 , 3 ],i就等於5。此時j+=1,j=1,繼續進行內部迴圈,因為5小於6,所以繼續進行交換,依次內推。整個迴圈過程如下:

[ 6 5 1 7 3 ]

[ 5 6 1 7 3 ]

[ 1 6 5 7 3 ]

[ 1 5 6 7 3 ]

[ 1 3 6 7 5 ]

[ 1 3 5 7 6 ]

[ 1 3 5 6 7 ]

經過上面的分析,大家可以看出,當第i個元素要與第0個元素到第i-1個元素進行比較的時候,第0個元素到第i-1個元素總是保持有序的(這一點,非常的總要,這也是插入排序的重要特點之一)。好了,既然是有序的,當我取出第i個元素進行比較的時候,如果第i個元素,小於第j個元素,那就意味者第i個元素要小於第j個元素到第i-1個元素了,後面的內部迴圈的比較就可以不進行比較了,這樣我們就可以節省下進行資料不比較的比較的資源的消耗了。改寫的程式碼如下:

<?
$rand_arr = [ 隨機陣列 ];
$array_count = count($rand_arr);
for($i=1;$i<$array_count;$i++){
    //改為從右到左進行比較排序
    for($j=$i-1;$j>=0;$j--){
        if($rand_arr[$j]>$rand_arr[$j+1]){
            //進行資料的交換
            $temp = $rand_arr[$j];
            $rand_arr[$j] = $rand_arr[$j+1];
            $rand_arr[$j+1] = $temp;
        }else{ //提前退出內層迴圈可以減少資料的比較
            break;
        }
    }
}
?>複製程式碼

好了,上面的優化方案,會幫助我們減少不比較的資料比較,但是插入排序中資料的交換次數還是很多,一次資料交換,相當於三次賦值操作,如果我們能夠降低資料的交換操作,用別的方式來實現資料的交換,就肯定能提高插入的排序的執行效率。下面我直接貼出程式碼,大家先看一下:

<?php
$rand_arr = [隨機陣列];
$array_count = count($rand_arr);
for($i=1;$i<$array_count;$i++){
    $compare_value = $rand_arr[$i];
    for($j=$i;$j>0;$j--){
        if($compare_value < $rand_arr[$j-1]){
            $rand_arr[$j] = $rand_arr[$j-1];
        }else{
            $rand_arr[$j] = $compare_value;
            break;
        }
    }
    if($j==0){
        $rand_arr[0]=$compare_value;
    }
}
?>複製程式碼

我說一下,上面程式碼的思路:加入需要的排序的陣列還是上面的 [ 6 5 1 7 3 ]。在初始的狀態下,i = j =1,都指向5,這時候取出第i個元素5,將該值賦予給比較變數compare_value,以這個變數為基準與第j-1的元素到第o個元素進行比較(從右到左),如果compare_value的值小於第j-1個元素,就將第j個原始設定為第j-1個元素的值。如果compare_value的值大於或者第j-1個元素,就將第j個元素設定為compare_value的值,根據插入排序的第0個元素到第j-1的元素的有序性,下面就不需要進行比較了。 排序的過程如下:

初始狀態下:[ 6 5 1 7 3 ] — i=j=1,都指向元素5,取出第i個元素作為比較值,也就是比較值等於5。使用比較值5與第j-1個元素(也就是第0個元素)進行比較,5<6,所以第j個元素(也就是第1個元素),賦值為6,得到以下結果[ 6 6 1 7 3 ]。此時j-=1,j=0,退出內部迴圈,所以第o個元素賦值為compare_value,也就是5,得到以下結果[ 5 6 1 7 3 ],這樣就完成了一層外部迴圈,而沒有采用一次資料的交換。然後依此類推,完成排序。這樣我們就做到了,減少資料交換的次數來提升執行效率的目的了。

下面,我用一個陣列長度為10000,陣列中的元素範圍在0-10000的隨機陣列作為測試用例,來測試一下,以上三種插入排序方式各自完成排序所需要的時間情況。

使用第一種方式:執行時間:24.062285900116

使用第二種方式:執行時間:14.136403083801

使用第三種方式:執行時間:6.7661621570587

可以說是,差距驚人,當陣列長度更大的時候,這種差距還回更大。

相關文章