前言:算法,一直是每个程序员的心病,确是程序的核心,很多人觉得算法很难,没错,但是世界上真的有很难的事情吗?如果不去尝试,只去抱怨,不去尝试,我觉得可能一辈子也就只能当一名普通的程序员了。有一句老话说的挺好,成功的人遇到一个难题,第一反应是想着怎么解决它,而不是先担心不会做,做不懂,如果不迈出第一步,后面只会更加难过。博主是个多愁善感的程序猿,不过我觉得有些东西,不该逃避的绝对不能去逃避。只有勇于面对困难,才能真正去战胜它,哈哈,感觉有点像心灵鸡汤了,好吧,开始我的初级算法学习之路了。
一、算法好坏的判别标准:
1.1 时间复杂度
无论是什么东西,总有一个判别的标准,算法也是,并不是说你说这个算法好就好,界内是有一个专门的标准的,在保证算法的准确性后,这个判别标准就是算法的时间复杂度,学过数据结构的同学不会对此陌生,评价一个算法流程的好坏,是先看时间复杂度的指标,我们在然后再分析不同数据样本下的实际运行时间,也就是常数项时间。
通常我们一般使用O(读作big 欧 )来表示,具体来说,在常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分,如果记为f(N),那么时间复杂度为O(f(N))。
一般来说,我们谈论算法的速度所指的并非是时间,而是操作数的增速,这一点我们可以把 f(N) 的曲线图画出来就会十分的清晰,随着输入的增加,运行时间将以什么样的速度增加才是我们真正关注的,且算法运行时间并不以秒作为单位,可能是次数、时间等
下面我用上家公司的一道面试算法题来大致说明:
题目大致是:有两个已排序的数组,分别是N=[ 1,2,4,5,6,7] M=[ 2,3,5,6,9,12,15,17] ,请用原生的语言将其交集求出,假设N是一个有n个元素的数组,M是有m个元素的数组,求出其算法时间复杂度(要求,不用任何关于数组内置方法或函数)
当初我碰到这道题的时候,就知道肯定是有多种算法来解决的,现在看来大致有着三种,每种的算法复杂度都不同。下面我用PHP语言结合代码对其进行说明,(本想用c的,不过真的忘了)
第一种算法,也是最为简单的,两个循环进行遍历即可:
<?php
$m = [2,3,5,6,9,12,15,17];
$n = [1,2,4,5,6,7];
$tempArray = []; //临时数组
for($i = 0;$i < count($n); $i++){
for($a = 0;$a < count($m); $a++){
//如果某个数组遍历到最后
if(!$n[$i]||!$m[$a]){
break 2;
}
//匹配到相等的则放入临时数组中
if($n[$i] ==$m[$a]){
$tempArray[] = $n[$i];
break;
}
}
}
var_dump($tempArray);
我们简单分析一下这种简单粗暴的方式,基本上不懂算法的人也会用这种方式,实际上就是遍历而已,那么这种所谓的算法的时间复杂度是多少呢?
这时候我们可以引入一个概念:语言频度
语句频度:一条语句的重复执行次数
我们可以看出两个循环的语句频度分别为m,n,则最内层的代码执行的语句频度为m*n,那么我们可以说这个算法的时间复杂度为O(n*m)
第二种算法:使用二分查找法
什么是二分查找法:二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列,实现的代码如下:
<?php
class Test
{
public function index()
{
$m = [2,3,5,6,9,12,15,17,20,34];
$n = [1,2,5,6,9,12,13,34,35];
$tempArray = [];
for($i = 0; $i < count($n); $i++) {
$temp = $this->serachValue($n[$i],$m);
if($temp){
$tempArray[] = $temp;
}
}
var_dump($tempArray);
}
public function serachValue($value,$arr=[])
{
$max = count($arr); //数组的长度
$lower = 0;
$high = $max - 1;
//当lower和high相等时说明找到了
while( $lower <= $high ){
$middle = intval(($lower + $high)/2);
if($value > $arr[$middle]){
$lower = $middle+1;
}elseif($value < $arr[$middle]){
$high = $middle-1;
}else{
return $arr[$middle];
}
}
}
}
$c = new Test();
$c->index();
二分查找的要求是数据结构必须是顺序存储的,而且是有序化的,从小到大或者从大到小都可以,以3在数组 M=[2,3,5,6,9,12,15,17]查找为例,主要是如下的步骤:
①:一开始,low为0,high为7,则middle为intval((low+high)/2) = 4 ,则 3 < $M[4] ,故high = middle-1 = 3
②: low 为0 ,high为3,middle为intval((low+high)/2) = 2 ,则 3 < $M[3] ,故high = middle -1 = 1
③:low为0 ,high为1,middle为intval((low+high)/2) = 1,则 3 = $m[1] ,return $m[1] 查找成功 假设数组个数为M个,那么在这个数组查找一个数的时间复杂度为O(log2M),那么在我们的这个算法中,因为数组已经是有序化的,那么我们则不需要对其进行排序,而N数组的遍历的时间复杂度为O(n),故该算法的时间复杂度应为 O(n)*O(log2M),相比遍历的那个算法,O(log2M)肯定要比O(M)小,故这种算法相对于遍历来说是更好的算法。
第三种算法是:外排的方式,这种方式就比较灵活了,我大致说下流程。
首先,先定义两个坐标变量,都分别指向数组第一个元素的位置,如图,n一开始指向的是1,m指向的是2,咋们按照谁指的值小谁就移动的规定来进行,流程大致如下:
①n指向的值比m小,所以n移动,其实此时可以看出,数组N的最小的都比数组M的最小的还要小,那么1肯定不在数组M中,因为两个数组都是从小到大排好序的。
②n指向2, 此时n指向的值和m指向的相等,匹配成功,写入临时数组,n右移
③n指向4 ,4比m指向的值2大,谁小谁动,故m右移,m指向3,同理,m还是得右移
④m指向的值为5,大于n指向的值4,说明4是找不到的,按照规定n右移
......
代码大致如下:
<?php
$M = [2,3,5,6,9,12,15,17,20,34];
$N = [1,2,5,6,9,12,13,34];
$tempArray = [];
$m = 0;
$n = 0;
while($n < (count($N)-1)||$m < (count($M)-1)){
if($N[$n] < $M[$m]){
$n += 1;
}elseif($N[$n] > $M[$m]){
$m += 1;
}else{
$tempArray[] = $N[$n];
$n +=1;
}
}
var_dump($tempArray);
最后我们简单总结一下三种算法的差异
第一种:直接遍历,时间复杂度为O(n*m) 效果较差
第二种:使用二分法 ,时间复杂度为O(n*log2m) 效果比上个好
第三种:使用外排法 ,时间复杂度为O(n+m),效果较好
哈哈,这就是算法的奥妙了。