vlambda博客
学习文章列表

PHP数据结构-线性查找与二分查找

线性查找与二分查找

欢迎来到查找的世界,在学习完各种数据结构之后,总算走到了这一步,不知道大家有什么感想呢?反正我是边学边忘,现在让我去说说图的那几个算法还是在蒙圈的状态中。不过学习嘛,就是一步一步的来,暂时搞不懂的东西其实也是可以放一放的。打破砂锅和坚持不懈当然是好的品德,但有些东西可能真的是需要时间去消化的,甚至可能是需要真实的项目经历才能彻底搞明白。在我们编程行业来说就是典型的这种实践的学习形式效果会更好,很多人在上大学的时候对于数据结构以及其它专业课都是以死记硬背为主,包括上了多少年班的同学可能都没有在业务代码中真正的使用过什么算法,所以理解它们确实是非常困难的。这时,我们可以暂时休息一下,转换一下思路,学习最主要的就是预习和复习,在这次学习完之后,将来再进行多次的复习,研究各种不同的资料,迟早有一天大家都能搞明白的。

今天的内容其实就非常简单了,可以说是除了线性表之外最简单的内容。我们只研究两个非常初级的查找,那就是顺序查找和折半查找。相信不少同学可能早就会了,一般培训机构讲数据结构和算法时,查找必讲二分,排序必讲冒泡,更不用说正规大学对口专业出身的同学了。当然,这两个也是非常简单的,不管你有没有基础,咱们一起来看看吧。

不管你是什么算法题,还是在实际的业务开发中,查找都是非常重要的,甚至可能比排序还要重要。想想你整天面向数据库编程是在干嘛?不就是 CRUD 嘛,其中大部的业务还都是以搜索查找居多,我们在优化数据库时,也主要是优化各种查询语句。当然,要说到数据库的查找那就太高深了,以后我们学习 MySQL 相关的知识时再详细讲解,特别是索引中的 B+ 树,就是数据结构和算法的核心思想的体现。好吧,不吹牛了,也不敢在这里多说了,因为自己也没研究透呢。

线性查找(顺序查找)

顾名思义,不管是叫线性还是叫顺序,很明显,就是一条数据一条数据的对比下去就好啦。

function SearchSeq($sk, $arr)
{
    for ($i = 0; $i < count($arr); $i++) {
        if ($sk == $arr[$i]) {
            echo $i, PHP_EOL;
            break;
        }
    }
    echo "线性查找次数:" . $i, PHP_EOL;
}

嗯,真的是连解释都不想解释了,这段代码要是看不懂的话就先去复习下基本的循环和条件判断语句吧!很明显,一次线性查找的时间复杂度就是 O(N) 。

二分查找(折半查找)

既然都这么简单,那么我们再直接给出折半查找的代码。

function SearchBin($sk, $arr){
    $left = 0;
    $right = count($arr) - 1;
    $i = 0;
    while ($left <= $right) {
        $i++;
        $mid = (int) (($left + $right) / 2);
        if ($arr[$mid] > $sk) {
            $right = $mid - 1;
        } else if ($arr[$mid] < $sk) {
            $left = $mid + 1;
        } else {
            echo $mid, PHP_EOL;
            break;
        }
    }
    echo "折半查找次数:" . $i, PHP_EOL;
}

折半查找的前提是数据必须是有序的,这样我们就可以根据数据问题的长度来获取中间的数,然后跟要对比的数进行比较,如果小于这个数,就在前一半数据中查找,如果大于这个数,就在后一半部分中进行查找。一会看例子再详细说明。

对比

两个算法其实都很简单,我们直接看看他们的运行情况和效率区别。

$arr = [5619254335677828164379123];

// 输入 56
fscanf(STDIN, "%d", $searchKey);

SearchSeq($searchKey, $arr);
// 6
// 线性查找次数:6

sort($arr);
print_r($arr);

SearchBin($searchKey, $arr);
// 8
// 折半查找次数:3

首先我们定义了一个数组,其实就是随便给了一些数据。然后输入一个数据,查找它在数组中的位置。比如我们在测试代码中输入了 56 ,线性查找是循环进行了 6 次,找到 56 所在的位置为下标 6 的位置。

对于折半查找来说,我们需要先给数组排序,这时 56 会排在下标为 8 的位置,而在折半查找的循环中,我们只循环了 3 次就找到了这个位置。是不是感觉快了很多,一下就快了一倍。这可不是它的真正实力哦,折半查找的真实实力是 对数 级别的效率,也就是它的时间复杂度为 O(logN) 。我们先来结合上面的代码看下它这三次循环都干了什么。

  • 第一次进入,mid 为 6 (0+13=13,除2),下标为 arr[6] 的值为 3 ,比 56 小,所以 left = 6+1 = 7

  • 第二轮循环,mid 为 10(7+13=20,除2),下标为 arr[10] 的值为 77 ,比 56 大,所以 right = 10-1 = 9

  • 第三轮循环,mid 为 9(7+9=16,除2),下标为 arr[8] 的值为 56,结束

其实很多猜数字的游戏也都是这么玩的,比如给你一个范围,0-100的数,猜他写下的是哪个数,最快最简单的方法也就是这种折半查找的方式,我们只需要最多 7 次就可以猜出 100 以内的数。很明显,这就是对数的威力。下面我们再来看一个更直观的,十万个有序的数,我们就找最后那一个数,看看顺序查找和折半查找能有多大差距。

$arr = range(1100000);
$searchKey = 100000;

SearchSeq($searchKey, $arr);
// 99999
// 线性查找次数:99999

SearchBin($searchKey, $arr);
// 99999
// 折半查找次数:17

嗨不嗨,这就是对数的威力!!我们需要 2 的 7 次方才能覆盖 100 以内的数,但我们只需要 2 的 17 次方,就能覆盖十万以内的数,这个效率差距还是随着 N 的越来越大而越来越明显的。

总结

今天的内容是不是很简单,虽说内容简单,但是我们却见识到了不同算法效率之间的巨大差异。当然,折半查找也有其本身的局限,那就是数据必须是的序的,当然,在合适的情况下我们也可以选用一个 O(logN) 的排序算法,这样总体的时间复杂度就还能保持在对数级别了。总之,先掌握好这些简单的内容,千万别在面试的时候连这一关都过不了哦!

测试代码:

https://github.com/zhangyue0503/Data-structure-and-algorithm/blob/master/6.查找/source/6.1线性查找与二分查找.php

参考文档:

《数据结构》第二版,严蔚敏

《数据结构》第二版,陈越

《数据结构高分笔记》2020版,天勤考研