a*算法的时间复杂度_算法时间复杂度的那些事

本文介绍了算法的时间复杂度分析方法,包括大O记法的概念、常见的时间复杂度、非递归及递归算法的时间复杂度分析技巧,并通过具体实例进行说明。

对于一个算法来说,我们如何评价它的好坏呢?一般来说就是通过程序解决问题的时间和空间。现在的计算机空间效率一般已经不是关注的重点了,但是时间效率仍然是算法关注的重点。而考查一个程序的运行时间,就需要将程序每次执行的操作表示成输入规模的函数。这就是时间复杂度分析。时间复杂度的分析也是算法面试中常考的问题,经过最近的学习,总结如下。

什么是大O记法

举几个例子来说,O(f(n))是一个集合,表示的是所有函数的增长趋势小于等于f(n)的函数集合。比如n属于O(n^2),500n+10属于O(n ^ 2).

对于定义来说就是,对于一个函数g(n)属于O(f(n))中,表示如下的公式:

1adf8c9f778e6862d071beb4fff44073.png

它表示的条件是:对于足够大的n,,g(n)的上界由f(n)的常数倍所确定,也就是说,存在大于0的常数c和非负数n0,使得:对于所有的n大于等于n0时,g(n)都是小于等于cf(n)的。

335cc294a0496b55bfa0d2b47b58fd82.png

比如前面所说500n+10小于等于510n^2.它的c=501。

大O记法的一些特性

8308d88eadc12cdde5b0eb179b43b67e.png

常见的时间复杂度

对于顺序结构的程序执行,它的时间复杂度就是O(1).

int a=10;

int b=5;

上面程序虽然执行了两步,但对于大O表示法来说,都是O(1),因为2相当于是系数,可以去掉。

下面这个循环结构的时间复杂度是O(n)

for(int i=0;i<n;i++)

{

int b=10

}

下面这个程序就是O(logn)的时间复杂度

int count=1;

while(count<n)

{

count=count*2;

}

由于每次执行count扩大两倍,那么就是2^ x=n,可以解得x=log以2为底的n的对数,所以时间复杂度就是O(logn)

双重循环就是O(n^2)的时间复杂度

for(int i=0;i<n;i++)

{

for(int j=0;j<n;j++)

{

//

}

}

非递归算法的时间复杂度分析

以查找数组中的最大值程序为例,分析计算一个非递归程序时间复杂度是如何计算的。

int MaxElement(int *A,int n)

{

int maxval=A[0];

for(int i=0;i<n;i++)

{

if(A[i]>maxval)

{

maxval=A[i];

}

}

return A[i];

}

可以看到,这个问题的输入规模是数组元素的个数决定的。由前面的学习可以知道,一个程序的时间复杂度是由程序之中大O最大部分决定的,所以只需要计算最大值就可以了,这个程序中的最大值是由for循环部分决定的。for循环内部进行了一步的比较操作,那么对于i从0到n-1,一共进行了n-1次比较,那么整个程序执行的次数就是:

5767a8b8ea90a7a5ef0da97ed5a66a83.png

所以时间复杂度是O(n),这个分析也证明了前面所说的for循环的时间复杂度是O(n).

非递归的时间复杂度分析的步骤如下:

1)首先找到问题的输入规模在哪里,例如本例的n

2)找出算法的基本操作(一般是最内层的循环)

3)建立一个算法基本操作的求和表达式,利用数学知识得到它的和

4)利用大O的一些准则简化表达式,得到最终的大O时间复杂度分析结果。

bool uniqueElements(int *A,int n){  for(int i=0;i-1;i++)  {    for(int j=i+1;j    {      if(A[i]==A[j])      {        return false;      }    }  }  return true;}

我们根据上面的一个算法,找出一个数组中是否包含重复元素,来实践一下刚才所说的分析方法,首先找到输入规模为n,但是这个算法如果在中间出现了相同元素,就结束了,所以说不仅仅取决于n,还取决于数组中数据的情况,这里谈论一下最坏的情况,就是遍历了整个数组。

那么此时的输入规模就是n,找到关键的执行步骤,双重循环中的判断语句,每次执行一次。在内循环之中j在i+1和n-1之间的每一个值都会比较一次。在外循环中,i从0到n-2的每个值,都会重复上面的过程一遍。因此可以得到:

6d6025ab6fc20f1c930004cc693865e8.png

此时就考验数学了。首先将内部的j循环展开。 

9391b2973a736262ef8be49894aa430c.png

此时两个求和式子分别求解 

b4d862b4a01d7ae1adbc0d5ec0d0a297.png

将两个式子合并,得到

ffef5da489f24809d7c0dc598b331fb7.png

利用大O的准则可以得到它的最坏时间复杂度就是O(n^2)。当然这种非递归的其实很好记,看到循环就可以判断,这么推导是能够清楚的知道到底是如何计算时间复杂度,可以理解其本质,虽然面试不会考推导,但对自己来说是一种提升。

这里面用到了两个数学里的求和公式:

9eba3a55657e86eb1d3a1e858e1fe2e6.png

递归算法的时间复杂度分析

其实我从刚开始学习数据结构,就对这个递归程序复杂度的分析很迷惑,不知道如何计算,只是知道去死记硬背一些排序算法,比如归并排序等的时间复杂度。直到最近重新学习,才发现有主定理这种东西。

递归算法的时间复杂度主要有两种方法来计算,要根据算法的实际情况来选择,一种是每次递归只是将数据规模减一或者减二这种的,比如斐波那契数列。另一种是类型归并排序和快速排序这种分治思想的递归算法,将问题的规模分成几份,分别求解。

迭代法

比如采用递归的算法计算n的阶乘。

int F(int n)

{

if(n==0) return 1;

else return F(n-1)*n;

}

我们假设F(n)所需要的执行的次数是C(n),那么就可以将C(n)用如下的公式表示出来了。

2abbaf04781516db609a3f91e84cf657.png

其中C(n-1)是用来计算F(n-1)的运算次数,而1是表示乘法的运算此时。而当n==0时,就是C(0)=1,因为直接返回了值。那么我们就可以根据上面的式子来进行迭代求解了。

c8fab7e5e4cd6d0f629b5d0ef3a0dc9f.png

逐步迭代可以迭代出以下的式子:

7ed05e3bfbef8026193cad02382357fb.png

所以这个算法的时间复杂度是O(n)。

下面我们在以著名的汉诺塔问题为例,这里不详细解释这个问题了,直接把递归程序拿过来借用,学习如何使用迭代法分析时间复杂度。

void hanNuo(int n,char A,char B,char C){  if(n>0)  {    hanNuo(A,C,B,n-1);    cout<<"A to C"<<endl;    hanNuo(B,A,C,n-1);  }}

还是用C(n)来表示hanNuo(n)的执行次数,那么就可以根据递归的公式来表示出C(n).

bf0b8acde481b8e33006fd646585eeee.png

这就是迭代法求解递归算法的时间复杂度分析的方法。

主定理

当递归函数的时间函数满足如下的关系时,就可以使用主定理了。

aede4de6f291dc2f664e96b01eeb2168.png

下面举例分析。

以归并排序为例,归并排序的代码这里就不贴了,首先是将两个问题分成相等的两部分,所以b=2,需要左侧递归和右侧递归,两部分那么a=2,将两部分合并的起来的f(n)的操作的时间复杂度是O(n).所以根据主定理可以得到O(n)=O(f(n)),所以时间复杂度为O(nlogn)。

在看下面的程序

int lianxi(int n){    if(n==0)    {        return 0;    }    return lianxi(n/4)+lianxi(n/4);}

根据主定理,可以看到a=2,b=4,而O(f(n))=O(1),因为只是进行了加法运算。所以比较

c000e8de7c84e7063aa4ee1e47a888f3.png

所以这个时间复杂度就是O(根号n)。

对于快速排序也是一样可以这么分析,这里不在详细分析了。

对公式不支持,所以都截成图片了。

f773f81f51b62cae6798f74f3305c792.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值