数据结构与算法——复杂度(一)

写在前面:学习C挺久了,但是一直都没有往数据结构与算法这方面看,近期接触一些笔试和面试题,发些了自身的不足。自己也思考了一番,要做的深一些和走的更远一些,数据结构和算法是必需要熟悉的。这系列的文章为本人的学习总结,若有什么错误,请各位指出。

复杂度分析(一)

我们编程的初衷是都想设计出运行更快、占用内存更小的程序,因此程序的复杂度分析在编程语言学习过程中尤为重要。想起以前我学习的毛病就是不管程序占多大跑多快,我不关系,只要功能实现了就行,就不慢慢深究里面的东西了。后来渐渐的发现这种学习方式不太行(工作上的话就另说了),所以慢慢的了解框架之后,现在开始研究的更细了。扯远了,回归正题。
复杂度分析简单点说就是评价程序执行效率的方法,有时间复杂度和空间复杂度。常用大O表示法来表示。如:O(m+n),O(1)等。

时间复杂度含义

含义:代码执行时间随着数据规模增长的变化趋势,即T(n) = O(f(n))。简言之就是评价代码的执行时间(评价而非计算)。
介绍个示例更直接些:
代码块1

int cal_sum(int n)
{
	int sum = 0;
	int i = 0, j = 0;
	for(i = 0; i< n; i++)
	{
		sum = i + j;
		for( j = 0; j < n; j++)
		{
		    sum = sum + i * j;
	    }
    }
}

假设每一条语句执行的时间为单位t;上面的代码块1中,3、4两行总执行2t,第5、7行总执行了2nt,第8、10行总执行了2tnn,所以这段代码片1的总执行时间为T = (2+2n+n * n * 2)t,这是一个关于n的2次多项式,可见T与运行次数n成正比,当n很大时,T的大小主要由2次项 2nn所决定。因此当我们抛弃常数项和一次项,可得T~n * n,进一步T(n) = O(n*n)。如下代码块2,第5、7行循环次数最多为n,占用时间最长。所以T(n) = O(n),。
//代码块2

int cal_sum(int n)
{
    int sum = 0;
    int i = 0, j = 0;
    for(i = 0; i< n; i++)
    {
   	sum +=  i;
    }
}

时间复杂度的分析方法

1. 集中关注循环次数最多的一段代码

在复杂度的分析过程中,我们通常会忽略系数、常数、低阶,只考虑最高阶对代码段的影响,也就是只关注循序次数最多的。如上面代码段1和2,就是从循环次数最多的角度去分析时间复杂度的。

2. 加法法则 :

加法法则:建立在方法1的基础上,寻找影响整段代码块最大的时间复杂度,这个复杂度即是整段代码的复杂度
这里贴一个别人写的代码块3

int cal(int n) {
   int sum_1 = 0;
   int p = 1;
   for (; p < 100; ++p) {
     sum_1 = sum_1 + p;
   }

   int sum_2 = 0;
   int q = 1;
   for (; q < n; ++q) {
     sum_2 = sum_2 + q;
   }
 
   int sum_3 = 0;
   int i = 1;
   int j = 1;
   for (; i <= n; ++i) {
     j = 1; 
     for (; j <= n; ++j) {
       sum_3 = sum_3 +  i * j;
     }
   }
 
   return sum_1 + sum_2 + sum_3;
 }

上述代码块3有3段代码部分,通过前面的分析可知
sum_1的时间复杂度为一常数,无论n怎么变化都不影响sum_1代码段;
sum_2的时间复杂度为O(n);
sum_3的时间复杂度为O(n * n);
所以,综合上述的时间复杂度,当n很大时,sum_3代码块对整个程序影响最大。所以代码块4的时间复杂度为O(n * n)。
抽象公式:
T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n)))
这貌似也没有体现出加法,那就再列一个体现加法法则的,如下代码块4

int cal(int n, int m) {
  int sum_1 = 0;
  int p = 1;
  for (; p < n; ++p) {
    sum_1 = sum_1 + p;
  }

  int sum_2 = 0;
  int q = 1;
  for (; q < m; ++q) {
    sum_2 = sum_2 + q;
  }
  }

上述代码块4中,sum_1代码块的时间复杂度为O(n);sum_2代码块的时间复杂度为O(m)。因为m,n的大小无法确定,所以我们就不能寻找最大值了,上述的抽象公式就不适用了。那么此时整段代码的时间复杂度就为 O(n)+O(m) = O(n+m)。

3. 乘法法则

乘法法则:多见于嵌套循环中,复杂度=嵌套内外乘积
抽象出一个公式:
T(n)=O(f(n))*O(g(n))=O(f(n)*g(n))
下面贴一个示例代码块5

void fun(int m, int n)
{
    int i , j;
    for (i = 0; i < n; i++)
    {
   	for (j = 0; j < m; j++)
   	{
   		scanf("%d", &matrix_a[i][j]);
   	}
   }
}

上述代码块5,为2层循环嵌套,应用乘法法则,这段代码的复杂度为O=O(n)*O(m)=O(n * m)。

常见的时间复杂度

1.O(1)式

代码执行的时间不随着某值n的增长而出现增长。即只要代码段中没有循环、递归、嵌套等,时间复杂度通通都是O(1)。如下代码块6

void fun(void)
{
    int i = 5;
    int j = 12;
    sum = i + j;
    sub = i * j;
    mod_ = i / j; 
}

2.O(logn)式与O(nlogn)

对数阶时间复杂度,这个比较难分析。贴个代码块7

int i=1;
while(i < n)
{
   i = 5 * i;
}

对代码块7进行时间复杂度分析:
第4行代码执行次数是最多的(其实第2行比第4行多执行1次),为了计算,就忽略了那一次。注意这里,第4行不是执行了n次,而是执行了5 * 5* 5 * …(i个5) = n ,即5^i = n,那么i=log⁡5ni = \log_5ni=log5n。所以这段代码的时间复杂度就为O(log⁡5n\log_5nlog5n)。如果5换成3,则复杂度为O(log⁡3n\log_3nlog3n)。但是为了更好的表示对数复杂度,我们通常忽略底数,只保留为O(log⁡n\log nlogn)。
对于O(nlog⁡nn\log nnlogn),也比较好理解了,把上述代码块8循环n次,按照乘法法则,就有了时间复杂度O(nlog⁡nn\log nnlogn)。如归并排序算法、快速插入算法的时间复杂度都是O(nlog⁡nn\log nnlogn)。示例代码块8如下:

int i = 1;
int j = 0;
while(j < n)
{	
  while(i < n)
  {
      i = 5 * i;
  }
  j++;
}

3.O(m+n)式与O(m*n)式
这两种已经在前面提到了。
因此常见的复杂度阶数:O(1)、O(logn)、O(n)、O(nlogn)、O(n^2)

总结:
1. 理解复杂度是一种代码执行效率的评价方式,表示算法的执行时间(占用空间)与数据规模之间的增长关系。
2. 掌握复杂度分析的3种方法,最大值法、加法、乘法。
3. 熟悉常见的复杂度O(1)、O(logn)、O(n)、O(nlogn)、O(n^2)
4. 多用复杂度分析方法去分析代码复杂度。
先写到这里,等以后有什么新启发了再来加一加。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值