演算法與資料結構之基礎排序演算法

吳軍旗發表於2018-01-09

之前讀劉未鵬的《暗時間》裡面就有這樣一個觀點“網際網路技術日新月異,程式設計師要學的技術變化的很快,但真正不變的東西其實很少,比如資料結構和演算法”,

原文請訪問番茄技術小站

磨刀不誤砍柴工

先做一些,準備工作,比如生成排序演算法需要的隨機陣列、陣列元素交換、測試排序演算法的效能、生成基本有序的資料等函式

<?php
/**
 * 排序演算法幫助函式
 * @author junqi1 <2018-1-7>
 */


/**
 * [generateRandomArray 生成有n個元素的隨機陣列,每個元素的隨機範圍為[rangeL, rangeR]]
 * @param  [type] $n      [description]
 * @param  [type] $rangeL [description]
 * @param  [type] $rangeR [description]
 * @return [type]         [description]
 */
function generateRandomArray($n, $rangeL, $rangeR) {

    assert($rangeL < $rangeR);

    $arr = array();
    for ($i = 0; $i < $n; $i++){
        $arr[$i] = rand($rangeL, $rangeR);
    }
    return $arr;
}

/**
 * [generateNearlyOrderedArray 產生n個幾乎有序的陣列, ]
 * @param  [type] $n         [description]
 * @param  [type] $swapTimes [順序陣列中調換的個數]
 * @return [type]            [description]
 */
function generateNearlyOrderedArray($n, $swapTimes){
    $arr = array();
    for ($i=0; $i < $n; $i++) { 
        $arr[$i] = $i;
    }

    //調換陣列
    for ($i=0; $i < $swapTimes; $i++) {
        $swap1 = rand(0, $n-1);
        $swap2 = rand(0, $n-1);
        swap($arr, $swap1, $swap2);
    }
    return $arr;
}

//陣列元素交換
function swap(&$arr, $i, $j){
    $tmp = $arr[$i];
    $arr[$i] = $arr[$j];
    $arr[$j] = $tmp;
}

function isSort($arr, $n){
    for ($i=0; $i < $n-1; $i++) {
        if ($arr[$i] > $arr[$i+1]) {
            return false;
        }
    }
    return true;
}

//測試排序演算法的效能
function testSort($sortName, $sorFunction, $arr, $n){
    $t1 = microtime(true);
    $sorFunction($arr, $n);
    $t2 = microtime(true);
    assert(isSort($arr, $n), "排序演算法錯誤!\n");
    echo "{$sortName}執行的時間為:". (($t2-$t1)).'s'."\n";
}
複製程式碼

為什麼需要學習O(n*n)複雜度的演算法

  • 基礎
  • 編碼簡單,易於實現,是一些簡單情景的自選
  • 在一些特殊的情況下,簡單的排序演算法更有效(比如,基本有序的情況下,插入排序的效率極高)
  • 簡單的排序演算法思想衍生出複雜的排序演算法
  • 作為子過程, 改進更復雜的排序演算法。

選擇排序演算法(selection sort)

定義(wiki)

選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。

程式碼實現

<?php
require('../Library/SortTestHelper.php');


/**
 * [selectSort 選擇排序]
 * @param  [type] &$arr [description]
 * @param  [type] $n    [description]
 * @return [type]       [description]
 */
function selectSort(&$arr, $n){
    for($i = 0; $i < $n; $i++){
        $minIndex = $i;
        for ($j=$i + 1; $j < $n; $j++) {
            if ($arr[$j] < $arr[$minIndex]) {
                swap($arr, $minIndex, $j);
            }
        }
    }
}


//main
// $n = 100;
// $arr = generateRandomArray($n, 0, $n);
// testSort("selectSort", "selectSort", $arr, $n);
複製程式碼

插入排序(insertionSort)

現實生活中,整理撲克牌撲克牌其實就是插入排序的應用

定義

插入排序(英語:Insertion Sort)是一種簡單直觀的排序演算法。它的工作原理是通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。

基本程式碼實現

/**
 * [insertSort 插入排序]
 * @param  [type] &$arr [description]
 * @param  [type] $n    [description]
 * @return [type]       [description]
 */
function insertSort(&$arr, $n){
    //從第2個元素開始,找尋它的位置,所以從i=1開始
    for ($i=1; $i < $n; $i++) {
    	//從第1個元素到第i個元素之間,尋找arr[i]的位置, j>0表示,最終比較的是第1個元素和第0個元素
    	//第一種寫法
    	// for ($j=i; $j > 0 ; $j--) {
    	// 	if ($arr[j-1] > arr[j]) {
    	// 		swap($arr, $j-1, $j);
    	// 	}else{
    	// 		break;
    	// 	}
    	// }

    	//第二種寫法:更簡潔
    	for ($j=$i; $j > 0 && $arr[$j-1] > $arr[$j]; $j--) {
    		swap($arr, $j-1, $j);
    	}
    }
}
複製程式碼

執行時間

//main
$n = 10000;
// $arr = generateRandomArray($n, 0, $n);
$arr = generateNearlyOrderedArray($n, 100);
$copy_arr = $arr;
testSort("selectSort", "selectSort", $arr, $n);
testSort("insertSortSeo", "insertSort", $copy_arr, $n);
複製程式碼

結果對比

selectSort執行的時間為:3.898992061615s
insertSort執行的時間為:4.036700963974s
複製程式碼

結果分析

按常理來說,插入排序應該比選擇排序時間短,但是實際卻長,這主要是因為插入排序中頻繁進行swap操作造成的,而一次swap,需要進行三次交換,那麼有沒有優化的方法呢?有的!

優化插入排序(insertSortSeo)

程式碼實現

/**
 * [insertSortSeo 優化的插入排序演算法:swap是進行三次交換,將這三次交換改變成一次賦值]
 * @param  [type] &$arr [description]
 * @param  [type] $n    [description]
 * @return [type]       [description]
 */
function insertSortSeo(&$arr, $n){
	for ($i=1; $i < $n; $i++) {
		//採用複製的方式
		$tmp = $arr[$i];
		for ($j=$i; $j > 0 && $tmp < $arr[$j-1]; $j--) {
			$arr[$j] = $arr[$j-1];
		}
		$arr[$j] = $tmp;
	}
}
複製程式碼

執行時間

//main
$n = 10000;
$arr = generateRandomArray($n, 0, $n);
// $arr = generateNearlyOrderedArray($n, 100);
$copy_arr = $arr;
testSort("selectSort", "selectSort", $arr, $n);
testSort("insertSortSeo", "insertSortSeo", $copy_arr, $n);
複製程式碼

結果對比

selectSort執行的時間為:4.1174781322479s
insertSortSeo執行的時間為:1.817638874054s
複製程式碼

插入排序的實際應用

插入排序在需要排序的資料基本有序的情況下,非常快,甚至比O(n*logN)時間複雜度的演算法還快,比如對伺服器日誌記錄進行排序時, 日誌資料基本上都是有序的,只有少數任務執行時間較長,造成資料順序不一致,這中情況下,非常適合插入排序演算法。

測試比較

首先生成基本有序的資料:

/**
 * [generateNearlyOrderedArray 產生n個幾乎有序的陣列, ]
 * @param  [type] $n         [description]
 * @param  [type] $swapTimes [順序陣列中調換的個數]
 * @return [type]            [description]
 */
function generateNearlyOrderedArray($n, $swapTimes){
    $arr = array();
    for ($i=0; $i < $n; $i++) { 
        $arr[$i] = $i;
    }

    //調換陣列
    for ($i=0; $i < $swapTimes; $i++) {
        $swap1 = rand(0, $n-1);
        $swap2 = rand(0, $n-1);
        swap($arr, $swap1, $swap2);
    }
    return $arr;
}
複製程式碼

執行時間

//main
$n = 10000;
// $arr = generateRandomArray($n, 0, $n);
$arr = generateNearlyOrderedArray($n, 100);
$copy_arr = $arr;
testSort("selectSort", "selectSort", $arr, $n);
testSort("insertSortSeo", "insertSortSeo", $copy_arr, $n);
複製程式碼

結果對比

selectSort執行的時間為:2.0646297931671s
insertSortSeo執行的時間為:0.040951013565063s
複製程式碼

插入演算法的延伸

希爾排序演算法就是插入演算法的延伸。


-------------------------華麗的分割線--------------------

看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。

個人部落格番茄技術小棧掘金主頁

想了解更多,歡迎關注我的微信公眾號:番茄技術小棧

番茄技術小棧

相關文章