当前位置: 移动技术网 > IT编程>开发语言>Java > 算法-复杂度

算法-复杂度

2020年07月16日  | 移动技术网IT编程  | 我要评论

1. 什么是时间复杂度?

what is Time Complexity?


定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数->进而分析T(n)随n的变化情况并确定T(n)的数量级
它表示随问题规模n的增大,算法执行时间的增长率是->f(n)就简称为时间复杂度 记作O(f(n))O(f(n))

先简单的介绍O(1) O(n) O(n2)O(1) \space O(n)\space O(n^2), 官方名称 常数阶,线性阶,平方阶

那么我们怎么去计算时间复杂度呢?这个算法执行时间的增长率是啥东东

推导大O阶:

  1. 用常数1取代运行时间中的所有加法常数
  2. 在修改后的运行次数函数中,只保留最高阶项
  3. 如果最高阶项存在而且不是1,则去除与这个项相乘的常数。结果就是大O阶

说实话这三行文字看起比较抽象,有点类似高数里面的求导?

1.1 常数阶

对于顺序结构的时间复杂度,下面这高斯算法,时间复杂度是O(1)O(1)

private static void gaussian(){
       Clock clock = Clock.systemUTC();
       System.out.println(clock.millis());	// 测试Java8提供的新方法
       long startTime=System.currentTimeMillis();   // 获取开始时间
       System.out.println(startTime);
       // 对于顺序结构的时间复杂度 跟n的大小无关 O(1)
       int sum, n=1000000;
       sum = (n+1)*n/2;
       System.out.println(sum);
       long endTime=System.currentTimeMillis(); // 获取结束时间
       System.out.println("程序运行时间: "+(endTime-startTime)+"ms");
   }

另外对于分支结构(不包含在循环结构中)而言,无论是false还是true,执行次数是恒定的,不会随着n的变化而变化

1.2 线性阶

线性阶的循环结构会稍微复杂一点,要确定算法程序的阶次,通常我们需要对某个确定的语句或者某个语句集代码块的运行次数,分析复杂度,关键分析循环结构的运行情况

private static void myLoop(){
       int n = 10000;
       for (int i = 0; i < n; i++) {
           // todoSomething
           System.out.println(i);
       }
   }

这个在循环体里面的代码块必须执行n次,所以时间复杂度是O(n)O(n)

1.3 对数阶
private static void logarithm(){
       int n = 10000, i=0;
       // i的每次变化阶级影响着循环的次数 所以需要根据公式计算而不是O(n)
       while(i < n){
           i = i*2;
       }
   }

由于i每次乘以2,有公式2x=n2^x=n,计算得出:x=log2nx=log_2n,所以这个循环的时间复杂度是log2nlog_2{n},也就是O(log2n)O(log_2{n})

1.4 平方阶
private static void mySquare(){
    int n = 100;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            System.out.print(i + j);
        }
        System.out.println();
    }
}

这是一个循环嵌套,内层循环是O(n)O(n),对于外层循环而言也是O(n)O(n);所以整个循环体时间复杂度是O(n2)O(n^2)
如果外层循环改为m,时间复杂度就变为O(mn)O(mn)
那么下面这个例子时间复杂度是多少呢?

private static void mySpecialSquare(){
   int n = 100;
   for (int i = 0; i < n; i++) {
   		// 这里j每次从i开始了 不是每次从0开始
       for (int j = i; j < n; j++) {
       		// 这里要求是O(1)的操作
           System.out.print(i + j);
       }
       System.out.println();
   }
}

这时候就不能单独分析内层和外层了,因为内层循环受到外层循环的条件i的影响;所以整体分析了:
0i<n0\le i<n,计算出内循环总的执行次数:
i=1ni=1+2+3+...+(n1)+n=n(n+1)2=n22+n2\color{blue} \displaystyle\sum_{i=1}^n i=1+2+3+...+(n-1)+n=\frac {n(n+1)} 2=\frac {n^2} 2 +\frac n 2
随着你的增大,只保留我们的最高项,然后去除系数,就得到O(n2)O(n^2)

好了经过徐师兄上面例子的前奏预热,下面摆几个姿势练练技术

int n,i,j;
function(n);// O(n)
for(i=0; i<n; i++){// O(n^2)
	function(n);
}
forfor(i=0; i<n; i++){// n(n+1)/2
	for (int j = i; j < n; j++) {
		// 执行O(1)操作
	}
}

这个其实就是把前面几种情形通过顺序结构连接起来而已,所以总的执行次数是:
f(n)=1+n+n2+n(n+1)2=32n2+32n+1\color{green}f(n)=1+n+n^2+\frac {n(n+1)} 2=\frac 32 n^2 + \frac32n+1
随着n的增大,这个代码的时间复杂度最终也是O(n2)O(n^2)

1.5 常见的时间复杂度
程序执行次数 时间复杂度O 非正式术语
12常数 O(1)O(1) 常数阶
2n+32n+3 O(n)O(n) 线性阶
3n2+2n+13n^2+2n+1 O(n2)O(n^2) 平方阶
log2nlog_2n O(logn)O(logn) 对数阶
3n+2nlog2n+13n+2nlog_2n+1 O(nlogn)O(nlogn) nlognnlogn
4n3+3n2+2n+14n^3+3n^2+2n+1 O(n3)O(n^3) 立方阶
2n2^n O(2n)O(2^n) 指数阶

它们的大小关系是(高中是不是证明过很多次了?(^^)):
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)\color{red}O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)
O(n3)O(n^3)及以上的时间复杂度过大,基本不会去考虑采用这个算法

1.6 最坏情况与平均情况

最坏情况运行时间:这是最重要的指标,一般在没有特殊指定下都是指最坏时间复杂度
平均运行时间:通过运行一定数量的实验数据得到的 是实际情况最有意义的

2. 什么是算法空间复杂度

通常我们在写代码的时候,完全可以用空间来换取时间,定义:计算算法所需的存储空间。
通常“复杂度”都只时间复杂度,也是研究的重点

总结:算法的优劣度直接决定了程序运行的效率
愚公移山的精神固然值得学习,但是发明炸药和挖掘机是更加聪明的做法

本文地址:https://blog.csdn.net/blackxc/article/details/107347312

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网