zoukankan      html  css  js  c++  java
  • 算法中的复杂度分析

    复杂度

    前言

    来复习下,算法体重经常聊到的复杂度

    算法中我们经常会从两个角度去考虑算法的优劣,那就是【时间维度】和【空间维度】

    时间复杂度

    时间复杂度:就是执行当前算法消耗的时间。

    当然我们这里讲的时间复杂度是个更加通用的描述,因为我们知道代码在不同中机器中执行的时间是不同的,性能好的机器可能就用的时间更短。

    所以我们这里的时间复杂度,用的是【大O符号表示法】,即T(n) = O(f(n))

    如何理解呢?先来个栗子

    func sum(n int)  int {
    	sum :=0
    	i:=3
    	for m:=0;m<n;m++ {
    		sum+=n*i+1
    	}
    	return sum
    }
    

    比如上面这段代码,循环了n次,那么时间复杂度就是O(n),如何分析呢?

    我们假定每行代码执行的时间是一个时间颗粒,那我们上面的例子,第2和3行一共两个时间颗粒,4和5行就是2*n个时间颗粒,总共就是(2n+2)个时间颗粒。

    当然这样算下来时间复杂度是T(n) = O(2n+2),大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度。

    如果 n 很大时,而公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。也就是2n中的2和常量2都可以忽略,所以时间复杂度就是T(n) = O(n)

    常数阶O(1)

    无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如:

    func sum(n int)  int {
    	sum :=0
    	i:=3
    	sum=sum*i
    	return sum
    }
    

    因为这段代码中没有一个系数,会导致代码的执行时间随系数的变化而变化,所以不管这个代码有多少行,都可以时间复杂度是 O(1)

    一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。

    线性阶O(n)

    比如我们刚开始的这个例子

    func sum(n int)  int {
    	sum :=0
    	i:=3
    	for m:=0;m<n;m++ {
    		sum+=n*i+1
    	}
    	return sum
    }
    

    代码中有个 for 循环,代码的执行时间会因为 n 的变化而变化,并且是线性增加或减少,所以这里用 O(n) 来表示他的时间复杂度。

    对数阶O(logN)

    func sum(n int)  int {
    	sum :=0
    	for sum<n {
    		sum+=n*2
    	}
    	return sum
    }
    

    这里同样也是一个循环,只不过每次都乘以2。也就是2的 x 次方大于等于 n 。 执行的次数就是 x 。

    time-log

    接着上面转换的结果,对于省略掉常数2,所以时间复杂度就是 O(logN)

    实际上,不管是以2为底、以3为底,还是以10为底,我们可以把所有对数阶的时间复杂度都记为 O(logn)

    线性对数阶O(nlogN)

    将上面的代码实例,外面在循环 n 次,时间复杂度就是下面我们要讨论的 O(nlogN)

    func sum(n int)  int {
    	sum :=0
    	for i:=0;i<n;i++{
    		for sum<n {
    			sum+=n*2
    		}
    	}
    	return sum
    }
    

    这个就不展开讨论了,内层循环是上面我们讨论的 O(logN) ,外面多了一层循环,所以时间复杂度就是 O(nlogN)

    平方阶O(n²)

    这个就很好理解了,就是两层代码的循环

    func sum(m,n int)  int {
    	sum :=0
    	for i:=0;i<m;i++{
    		for j:=0;j<n;j++ {
    			sum+=j
    		}
    	}
    	return sum
    }
    

    当然有三层循环就是 O(n³),依次类推

    空间复杂度

    空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。

    来个栗子分析下

    func test(n int) map[int]struct{} {
    	testMap := make(map[int]struct{}, n)
    
    	for i := 0; i < n; i++ {
    		testMap[i] = struct{}{}
    	}
    	return testMap
    }
    

    比如上面的这段代码,空间复杂度是 O(n)

    我们可以看到 for 开始的时候申请了一个变量 i ,这是常量级别的,跟数据规模 n 没有关系,所以我们可以忽略。我们初始化了一个长度为 n 的 testMap。testMap 的长度会根据 n 的变化而变化,所以这段代码的空间复杂度就是 O(n)。

    常数阶O(1)

    这个很简单

    func sum(n int)  int {
    	sum :=0
    	i:=3
    	sum=sum*i
    	return sum
    }
    

    因为没有变量跟随数据规模 n 的变化而变化,所以空间复杂度就是 O(1)。

    平方阶O(n²)

    看个栗子

    func test(n int) [][]int {
    	var sil = make([][]int, n)
    
    	for i := 0; i < n; i++ {
    		for j := 0; j < n; j++ {
    			sil[i] = append(sil[i], j)
    		}
    	}
    	return sil
    }
    

    定义了二维切片,随着两层循环,分别申请了 n*n 个空间的大小,所以空间复杂度就是 O(n²)

    当然有二维切片就是 O(n³),依次类推

    最好、最坏情况时间复杂度

    最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。

    最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。

    比如这段代码

    func sum(n, m int) int {
    	sum := 0
    	for i := 0; i < n; i++ {
    		if sum == m {
    			return sum
    		}
    		sum += i * 2
    	}
    	return sum
    }
    

    如果在第一次循环的时候,sum == m 就结束程序,那么这个时候的时间复杂度就是最好时间复杂度。

    当然如果上面的 for 循环,遍历到最后一次也没满足 sum == m,那么这个时候的时间复杂度就是最坏情况时间复杂度。

    平均情况复杂度

    顾名思义就是,最好和最坏的时间复杂度的平均值。

    比如上面的那个例子

    引入概率之后,前面那段代码的加权平均值为(3n+1)/4。用大O表示法来表示,去掉系数和常量,这段代码的加权平均时间复杂度仍然是O(n)。

    均摊时间复杂度

    对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。

    其实也可以认为均摊时间复杂度就是一种特殊的平均时间复杂度。

    总结

    这里大概介绍了空间复杂度和时间复杂度

    时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。类比一下,空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。

    这里的计算使用的是【大 O 复杂度表示法】,时间复杂度和空间复杂度只和数据规模 n 有关系,公式中的低阶、常量、系数三部分不影响增加趋势,所以不收这三个的影响。

    参考

    【算法的时间与空间复杂度(一看就懂)】https://zhuanlan.zhihu.com/p/50479555
    【数据结构与算法之】https://time.geekbang.org/column/intro/100017301
    【空间复杂度和时间复杂度】https://boilingfrog.github.io/2021/10/03/算法中的复杂度分析/

  • 相关阅读:
    平衡二叉树之RB树
    平衡二叉树之AVL树
    实现哈希表
    LeetCode Median of Two Sorted Arrays
    LeetCode Minimum Window Substring
    LeetCode Interleaving String
    LeetCode Regular Expression Matching
    PAT 1087 All Roads Lead to Rome
    PAT 1086 Tree Traversals Again
    LeetCode Longest Palindromic Substring
  • 原文地址:https://www.cnblogs.com/ricklz/p/15365070.html
Copyright © 2011-2022 走看看