全面了解歸並排序算法及代碼實現


在之前我寫過關於歸並排序的介紹,《排序算法學習之路——歸並排序》。據現在已經有很長時間了。現在再重新進行規整,對歸並排序再從代碼層面詳細說一下。

歸並排序算法

按照慣例,對於排序算法。我們還是先羅列概念

歸並排序是建立在歸並操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合並成一個有序表,稱為二路歸並。


合並

通過概念我們也能看出,既然是歸並排序,那核心的問題就是如何進行歸並了。這可以歸結為從小往大的一個合並問題。

給定我們一組數據

 

我們通過分治策略,將其拆分。直到不能拆為止,想要達到的效果如下

 

這就是我們最終用來進行歸並的拆分后的數組。下面開始合並

 

我們可以看到,每兩個數組進行合並。合並之后的新數組是有序的。然后新數組之間再兩兩合並,直到合並為一個最終的數組。

下面我們以最后一步合並為例子,介紹一下兩個數組之間合並的細節步驟。

 

第一步、 sl 和 sr位置上的元素進行比較,值小的一方的元素放入新數組,然后對應的索引 sl/sr 向前進一位。這里 sl位置上的2小,因此將2 放入新數組,sl移到該組的下一個元素

 

第二步、再次將 sl 和 sr 位置上的元素進行比較,3 比 6 小,因此 3放入新數組,sl再次移動到下一個元素

 

第三步、二者繼續比較,此時 sr上的 6 要比 sl上的7小。因此將6放入新的數組,sr移動到該組的下一個元素

 

第四步、重復上面的比較過程,直到有一組先於另一組全部放入到新數組中

 

最后,此時的 sl一組已經全部排序完成了,而對於 sr一組剩余的元素可以直接放入新數組。因為每一組之內的元素都是有序的。

 

此時我們看到整個歸並過程已經完成了。下面我們看一下合並過程的代碼(以下代碼用 PHP 編寫)

function Merge($arr,$l,$m,$r)
{
    $t = $arr;
    $lstart = $l;
    $rstart = $m+1;

    while($l < $r) {
        if($lstart > $m || $rstart > $r) break;
        if($arr[$lstart] > $arr[$rstart]) {
            $t[$l++] = $arr[$rstart++];
        }else{
            $t[$l++] = $arr[$lstart++];
        }
    }

    $start = $l;
    $end = $r;

    if($lstart <= $m) {
        $start = $lstart;
        $end = $m;
    }elseif($rstart <= $r) {
        $start = $rstart;
        $end = $r;
    }

    while($start <= $end) {
        $t[$l++] = $arr[$start++];
    }
    $arr = $t;

    return $arr;
}

 


拆分

上面我們看到了歸並排序的核心的過程,合並。但是只是有合並的過程還是不完整的,因為給到我們的原始數據是一組完整的數據。因此在合並之前我們應該先對其進行拆分。

拆分的過程比較簡單,它不會涉及到排序的問題,只是拆就完了。

 

這里拆分過程的代碼可以分為兩種方式:遞歸實現和非遞歸實現

下面我們分別看一下兩種不同的拆分代碼

遞歸

遞歸方式代碼就非常簡單了,我們只需要設定遞歸終止條件,然后按照一個整體的輪廓寫代碼就可以了

function MergeSort(&$arr,$l,$r)
{
    if($l >= $r) {
        return;
    }
    $m = floor(($l+$r) / 2);

    MergeSort($arr,$l,$m);
    MergeSort($arr,$m+1,$r);
    $arr = Merge($arr,$l,$m,$r);
}

 

我們可以看到,代碼很簡單。按照深度優先方式,先拆左邊,然后再拆右邊。最后調用我們上面的Merge() 函數進行合並就行了。

非遞歸

非遞歸的方式代碼顯得就有點復雜了,在上面使用遞歸的方式拆分的過程中,其實我們只是設定了終止遞歸的條件,其他的細節不用考慮的。但是非遞歸方式就必須需要考慮細節了。

因為拆分的過程是一個深度優先的過程,針對深度優先這里就需要用到棧機制。

其實,遞歸底層的實現機制也是借助的棧的機制實現的。

這里我們看一下代碼

function MergeSort(&$arr)
{
    $stack = []; //  初始化一個棧
    $toMergeStack = []; // 用於歸並的棧
    $l = 0;
    $r = count($arr) - 1;

    $tmp = [$l,$r,floor(($l+$r)/2)];

    array_push($stack,$tmp);
    array_push($toMergeStack,$tmp);

    while(!empty($stack)) {
        $s = array_pop($stack);
        if($s[0] < $s[2]) {
            array_push($stack,[$s[0],$s[2],floor(($s[0]+$s[2])/2)]);
            array_push($toMergeStack,[$s[0],$s[2],floor(($s[0]+$s[2])/2)]);
        }

        if($s[2]+1 < $s[1]) {
            array_push($stack,[$s[2]+1,$s[1],floor(($s[2]+1+$s[1])/2)]);
            array_push($toMergeStack,[$s[2]+1,$s[1],floor(($s[2]+1+$s[1])/2)]);
        }
    }

    // 開始合並
    while(!empty($toMergeStack)){
        $s = array_pop($toMergeStack);
        $arr = Merge($arr,$s[0],$s[2],$s[1]);
    }
}

我們再看代碼明顯多出許多來。但是這個過程並不復雜,理解了非遞歸的方式更有助於我們對歸並排序的理解。


免責聲明!

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



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