为什么要学算法?当然是为了装13呀,233。好吧,其实是因为最近测试并发的感受。
当代码能够缩短运行时间,运行次数,就可以给项目节省硬件的钱了。什么??你说硬件的钱关你屁事?老弟,你要这样想,这事关系到老板,也就关系到技术负责人。技术负责人不能让老板发现他带的团队,写的代码运行效率低下。所以他面你的时候需要你懂算法,并不是为了装13。
同时的,机器多了,需要招运维。每年要多租很多台服务器。老板只是把花在这些上面的一部分换成了你的薪资。
当应用响应缓慢,丢失用户也是一件损失很大的事情。
二分查找
上面的是二分查找,下面的是顺序查找,都是查找1,看上面的即可。
通过动态图,你就可以知道二分查找的核心就是每一次运行都是找最中间的值,去判断是否大于要查找的值。
往左,查找1
/**
* 二分查找
*
* 假设数据是按升序排序的,代码中注释的数值为第一轮查找
* @param array $arr 一个有十个数字的数组
* @param int $search 搜索值为1
* @return bool|int
*/
function twoPoints($arr, $search)
{
$l = 0; // 最左为0
$r = count($arr) - 1; // 最右9
while($l <= $r) { // 当0小于等于9
$mid = floor(($l + $r) / 2); // 向下取整,获取中值,为4
if ($arr[$mid] == $search) { // 如果中值等于要搜索的值,就直接返回了
return $mid;
} elseif($arr[$mid] > $search) { // 如果数组的下标[4]的值(4)大于要搜索的值(1)
$r = $mid - 1; // 将下标减1,使下标中的数值[4]变为[3]并赋给最右的变量。------ 此时$arr[中值-1=3],最右的下标值为3,意味着第一轮的结果:$arr = [0, 1, 2, 3]
} elseif ($arr[$mid] < $search) {
$l = $mid + 1;
}
}
return false; // 所要查找的值,数组中不存在
}
$arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
$result = twoPoints($arr, 1); // 返回值是下标键
echo $arr[$result];
while($l <= $h)
为什么要小于等于?因为如果你只是小于,搜索10的话,条件不成立就不会进入循环。
floor(($h + $l) / 2)
为什么要向下取整?因为单数的话除以2就不为整数。作为下标用的,虽然不加取整也可以,但是其它的语言就不一定了。
第二轮查找
/**
* 二分查找
*
* 此时,$arr = [0, 1, 2, 3],$l = 0,$r = 3
*/
function twoPoints($arr, $search)
{
/*
无视即可,这里讲的是while循环,不是一个新的方法,现在第二轮仍在循环内
$l = 0; // 最左为0
$r = count($arr) - 1; // 最右9
*/
while($l <= $r) { // 当0小于等于3
$mid = floor(($l + $r) / 2); // 向下取整,获取中值,为1
if ($arr[$mid] == $search) { // 如果中值等于要搜索的值,就直接返回了,1==1
return $mid;
} elseif($arr[$mid] > $search) {
$r = $mid - 1;
} elseif ($arr[$mid] < $search) {
$l = $mid + 1;
}
}
return false;
}
往右,查找8
<?php
/**
* 二分查找
*
* 假设数据是按升序排序的,代码中注释的数值为第一轮查找
* @param array $arr 一个有十个数字的数组
* @param int $search 搜索值为8
* @return bool|int
*/
function twoPoints($arr, $search)
{
$l = 0; // 最左为0
$r = count($arr) - 1; // 最右9
while($l <= $r) { // 当0小于等于9
$mid = floor(($l + $r) / 2); // 向下取整,获取中值,为4
if ($arr[$mid] == $search) { // 如果中值等于要搜索的值,就直接返回了
return $mid;
} elseif($arr[$mid] > $search) { // 如果数组的下标[4]的值(4)大于要搜索的值(8),此轮不大于,所以往下看
$r = $mid - 1;
} elseif ($arr[$mid] < $search) { // 否则如果数组的下标[4]的值(4)小于要搜索的值(8)
$l = $mid + 1; // 将下标加1,使下标中的数值[4]变为[5]并赋给最左的变量。------ 此时$arr[中值+1=5],最左的下标值为5,意味着第一轮的结果:$arr = [5, 6, 7, 8, 9]
}
}
return false; // 所要查找的值,数组中不存在
}
$arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
$result = twoPoints($arr, 8); // 返回值是下标键
echo $arr[$result];
第二轮循环
/**
* 二分查找
*
* 此时,$arr = [5, 6, 7, 8, 9],$l = 5,$r = 9
*/
function twoPoints($arr, $search)
{
/*
无视即可,这里讲的是while循环,不是一个新的方法,现在第二轮仍在循环内
$l = 0; // 最左为0
$r = count($arr) - 1; // 最右9
*/
while($l <= $r) { // 最左满足小于等于最右,5<=9
$mid = floor(($l + $r) / 2); // (5+9)/2=7,中值为7
if ($arr[$mid] == $search) { // 如果中值等于要搜索的值,就直接返回了
return $mid;
} elseif($arr[$mid] > $search) {
$r = $mid - 1;
} elseif ($arr[$mid] < $search) { // 否则如果数组的下标[7]的值(7)小于要搜索的值(8)
$l = $mid + 1; // 将下标加1,使下标中的数值[7]变为[8]并赋给最左的变量。------ 此时$arr[中值+1=8],最左的下标值为8,意味着第二轮的结果:$arr = [8, 9]
}
}
return false;
}
第三轮循环
/**
* 二分查找
*
* 此时,$arr = [8, 9],$l = 8,$r = 9
*/
function twoPoints($arr, $search)
{
/*
无视即可,这里讲的是while循环,不是一个新的方法,现在第三轮仍在循环内
$l = 0; // 最左为0
$r = count($arr) - 1; // 最右9
*/
while($l <= $r) { // 最左满足小于等于最右,8<=9
$mid = floor(($l + $r) / 2); // (8+9)/2=8,中值为8
if ($arr[$mid] == $search) { // 如果中值等于要搜索的值,就直接返回了,8==8
return $mid;
} elseif($arr[$mid] > $search) {
$r = $mid - 1;
} elseif ($arr[$mid] < $search) {
$l = $mid + 1;
}
}
return false;
}
简化
/**
* 二分查找
*
* @param array $arr 一个有十个数字的数组
* @param int $search 搜索值为8
* @return bool|int
*/
function twoPoints($arr, $search, $left, $right)
{
while($left <= $right) {
$mid = floor(($left + $right) / 2);
if ($arr[$mid] == $search) {
return $mid;
} elseif($arr[$mid] > $search) {
$right = $mid - 1;
} elseif ($arr[$mid] < $search) {
$left = $mid + 1;
}
}
return false;
}
$arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
$result = twoPoints($arr, 8, 0, count($arr) -1);
echo $arr[$result];
递归的写法
上面的left和right是为了好理解,其实应该理解为最高的键值与最低的键值比较恰当,毕竟是比大小嘛。
/**
* @param array $arr 数组
* @param int $search 要搜索的值
* @param int $low 最低位
* @param int $top 最高位
* @return bool|int
*/
function twoPoints($arr, $search, $low, $top)
{
if ($low <= $top) {
$mid = floor(($low + $top) / 2);
if ($arr[$mid] == $search) {
return $mid;
} elseif ($arr[$mid] <= $search) {
return twoPoints($arr, $search,$mid+1, $top);
} else {
return twoPoints($arr, $search,$low, $mid-1);
}
} else {
return false;
}
}
$arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
$result = twoPoints($arr, 8, 0, count($arr) -1);
echo $arr[$result];
与上面不同的是:递归时,要传最低位或最高位。
那么,为什么要用二分查找?
如果让你在$arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
中查找8,最简单的怎么做?
顺序查找
<?php
$arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
$search = 8;
foreach ($arr as $value) {
if ($value == $search) {
echo $value;
}
}
但是,现在有个问题。这里遍历了九次才找到8,而我们上面的二分查找只用了三次。差别只有六次嘛,那换成亿次呢?
总结:
1、当中值小于要搜索的数值,那么中值+1。
2、取(中值+1的下标键+最高下标键)÷ 2
向下取整,得出新的中值。如果新的中值等于要搜索的值就返回,否则重复1。
3、当中值大于要搜索的数值,那么中值-1。
4、取(中值-1的下标键+最低下标键)÷ 2
向下取整,得出新的中值。如果新的中值等于要搜索的值就返回,否则重复3。
当每次获取一个区间时,区间外的数值就被排除了。