时间复杂度-算法评价的标准

  前言:算法,一直是每个程序员的心病,确是程序的核心,很多人觉得算法很难,没错,但是世界上真的有很难的事情吗?如果不去尝试,只去抱怨,不去尝试,我觉得可能一辈子也就只能当一名普通的程序员了。有一句老话说的挺好,成功的人遇到一个难题,第一反应是想着怎么解决它,而不是先担心不会做,做不懂,如果不迈出第一步,后面只会更加难过。博主是个多愁善感的程序猿,不过我觉得有些东西,不该逃避的绝对不能去逃避。只有勇于面对困难,才能真正去战胜它,哈哈,感觉有点像心灵鸡汤了,好吧,开始我的初级算法学习之路了。

一、算法好坏的判别标准:

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),效果较好

哈哈,这就是算法的奥妙了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MClink

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值