1. 程式人生 > >算法筆記-二分查找

算法筆記-二分查找

str 指定元素 auth 排序 條件 通過 req 有序數組 中間

【二分查找】 二分查找針對的是一個有序的數據集合,查找思想有點類似分治思想。每次都通過跟區間的中間元素對比,將待查找的區間縮小為之前的一半,直到找到要查找的元素,或者區間被縮小為 0。 二分查找是一種非常高效的查找算法,時間復雜度是 O(logn)。O(logn) 這種對數時間復雜度,是一種極其高效的時間復雜度,有的時候甚至比時間復雜度是常量級O(1) 的算法還要高效。二分查找更適合處理靜態數據,也就是沒有頻繁的數據插入、刪除操作。 使用循環和遞歸都可以實現二分查找。 二分查找應用場景的局限性:
  • 二分查找依賴的是順序表結構,簡單點說就是數組。(鏈表不可以)
  • 二分查找針對的是有序數據。(如果數據沒有序,我們需要先排序。)
  • 數據量太大不適合二分查找。
四種常見的二分查找變形問題 1.查找第一個值等於給定值的元素 2.查找最後一個值等於給定值的元素 3.查找第一個大於等於給定值的元素 4.查找最後一個小於等於給定值的元素 適用性分析 1.凡事能用二分查找解決的,絕大部分我們更傾向於用散列表或者二叉查找樹,即便二分查找在內存上更節省,但是畢竟內存如此緊缺的情況並不多。 2.求“值等於給定值”的二分查找確實不怎麽用到,二分查找更適合用在”近似“查找問題上。比如上面講幾種變體。
<?php

/**
 * 二分查找 查找=find的元素
 * @param array $numbers
 * @param [type] $find
 
*/ function binarySearch(array $numbers, $find) { $low = 0; $high = count($numbers) - 1; return search($numbers, $low, $high, $find); } function search(array $numbers, $low, $high, $find) { /** * notice1 循環退出條件 */ if ($low > $high) { return -1; } /** * notice2 mid計算
*/ $mid = $low + (($high - $low) >> 1); if ($numbers[$mid] > $find) { //notice3 high值更新 return search($numbers, $low, $mid - 1, $find); } elseif ($numbers[$mid] < $find) { //notice4 low值更新 return search($numbers, $mid + 1, $high, $find); } else { return $mid; } } /** * 求數字的平方根,保留6位小數 * @param [type] $number */ function squareRoot($number) { if ($number < 0) { return -1; } elseif ($number < 1) { $min = $number; $max = 1; } else { $min = 1; $max = $number; } $mid = $min + ($max - $min) / 2; while (getDecimalPlaces($mid) < 6) { $square = $mid * $mid; if ($square > $number) { $max = $mid; } elseif ($square == $number) { return $mid; } else { $min = $mid; } $mid = $min + ($max - $min) / 2; } return $mid; } /** * 計算數字小數點後有幾位數字 * @param [type] $number */ function getDecimalPlaces($number) { $temp = explode(., $number); return @strlen($temp[1]); } // 測試二分查找給定值 $numbers = [0, 1, 2, 3, 3, 4, 5, 6, 7, 9]; $find = 6; echo binarySearch($numbers, $find); echo "\n"; //測試求平方根 echo squareRoot(3); echo "\n";
<?php

/**
 * 找到第一個=value的元素
 * @param array $numbers
 */
function findFirstEqual(array $numbers, $find)
{
    $length = count($numbers);
    $low = 0;
    $high = $length - 1;
    while ($low <= $high) {
        $mid = $low + (($high - $low) >> 1);
        if ($numbers[$mid] > $find) {
            $high = $mid - 1;
        } else if ($numbers[$mid] < $find) {
            $low = $mid + 1;
        } else {
            /**
             * 如果是第一個元素,或之前一個元素不等於我們要找的值
             * 我們就找到了第一個=find的element
             */
            if ($mid == 0 || $numbers[$mid - 1] != $find) {
                return $mid;
            } else {
                $high = $mid - 1;
            }
        }
    }

    return -1;
}

/**
 * 找到最後一個=find的元素
 * @param array $numbers
 * @param [type] $find
 */
function findLastEqual(array $numbers, $find)
{
    $length = count($numbers);
    $low = 0;
    $high = $length - 1;
    while ($low <= $high) {
        $mid = $low + (($high - $low) >> 1);
        if ($numbers[$mid] > $find) {
            $high = $mid - 1;
        } else if ($numbers[$mid] < $find) {
            $low = $mid + 1;
        } else {
            /**
             * 如果mid是最後一個元素的index
             * 或mid後一個元素!=我們要找的值
             * 則找到了最後一個=find的value
             */
            if ($mid == $length - 1 || $numbers[$mid + 1] != $find) {
                return $mid;
            } else {
                $low = $mid + 1;
            }
        }
    }

    return -1;
}

/**
 * 找到第一個大於等於find的元素
 * @param array $numbers
 * @param [type] $find
 */
function findFirstGreaterEqual(array $numbers, $find)
{
    $length = count($numbers);
    $low = 0;
    $high = $length - 1;
    while ($low <= $high) {
        $mid = $low + (($high - $low) >> 1);
        if ($numbers[$mid] >= $find) {
            if ($mid == 0 || $numbers[$mid - 1] < $find) {
                return $mid;
            } else {
                $high = $mid - 1;
            }
        } else {
            $low = $mid + 1;
        }
    }
    return -1;
}

/**
 * 找到最後一個小於等於find的元素
 * @param array $numbers
 * @param [type] $find
 */
function findLastLessEqual(array $numbers, $find)
{
    $length = count($numbers);
    $low = 0;
    $high = $length - 1;
    while ($low <= $high) {
        $mid = $low + (($high - $low) >> 1);
        if ($numbers[$mid] <= $find) {
            if ($mid == $length - 1 || $numbers[$mid + 1] > $find) {
                return $mid;
            }
            $low = $mid + 1;
        } else {
            $high = $mid - 1;
        }
    }
    return -1;
}


/**
 * 循環數組中找指定元素
 * @param array $numbers
 * @param [type] $find
 *
 * @return void
 * @date 2018/11/27
 * @author yuanliandu <[email protected]>
 */
function searchCircularArray(array $numbers, $find)
{
    $length = count($numbers);
    $low = 0;
    $high = $length - 1;

    while ($low <= $high) {
        $mid = $low + (($high - $low) >> 1);
        if ($numbers[$mid] === $find) {
            return $mid;
        }

        if ($numbers[$low] > $numbers[$mid]) {
            // 後半部分是有序數組
            if (($numbers[$mid] < $find) && ($numbers[$high] >= $find)) {
                if ($numbers[$high] === $find) return $high;
                //在後半個區間內
                $low = $mid + 1;
            } else {
                $high = $mid - 1;
            }
        } else {
            // 前半部分是有序的
            if (($numbers[$low] <= $find) && ($numbers[$mid] > $find)) {
                // 在有序區間內
                if ($numbers[$low] === $find) return $low;
                $high = $mid - 1;
            } else {
                $low = $mid + 1;
            }
        }

    }
    return -1;
}

/***
 * 測試
 */
$numbers = [1, 2, 3, 3, 3, 4, 5, 6, 8, 11, 13];
$find = 3;

var_dump(findFirstEqual($numbers, $find));//找到第一個等於find的元素,結果:2
var_dump(findFirstGreaterEqual($numbers, $find));//找到第一個大於等於find的元素,結果:2
var_dump(findLastEqual($numbers, $find));//找到最後一個=find的元素,結果:4
var_dump(findLastLessEqual($numbers, $find));//找到最後一個小於等於find的元素,結果:4


//測試在循環數組中找到指定數字
$numbers = [9, 10, 1, 2, 3, 4, 5, 6, 7, 8];
$find = 2;
var_dump(searchCircularArray($numbers, $find)); //結果:3

算法筆記-二分查找