一次搞定所谓的算法复杂度

算法复杂度其实也没有啥。

目录

概念

说明几点

时间复杂度

怎么确定时间复杂度?

时间复杂度是O(log n)的情况

时间复杂度是O(m+n)、O(m*n)的情况

空间复杂度

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


概念

时间复杂度:全称渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系

空间复杂度:全称渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系

说明几点

1,数据结构就是指一组数据的存储结构。算法就是操作数据的一种思路或方法。

2,数据结构和算法是相辅相成的。数据结构是为算法服务的,算法一般作用在特定的数据结构之上。

3,复杂度用大写O表示,如O(n)等。

4,在复杂度的描述中O(1000n)与O(n)一致,都写成O(n),即常量可以被忽略(不论常量有多大,不论它耗时多少)。

时间复杂度

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

怎么确定时间复杂度?

1,只关注循环执行次数最多的一段代码

func demo(){
	sum := 0
	for i:=0;i<=n;i++{
		sum += i
	}
}

如上代码中,定义sum是常量级的执行时间,对复杂度来说没有影响,而for循环数量级是n,换句话说这段代码被执行了n次,它的时间复杂度就是O(n)

2,嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

func demo1() {
	sum := 0
	for k := 0; k <= n; k++ { //O(n²)
		for h := 0; h <= n; h++ {
			sum = sum + k*h
		}
	}
}

上面代码是嵌套循环,需要遍历k*h次,数量级就是O(k*h),即它的时间复杂度是O(n²),这个很容易看出来。

3,总复杂度等于量级最大的那段代码的复杂度

func demo2() {
	sum := 0
	for i := 0; i <= 1000; i++ { // 忽略
		sum += i
	}

	for j := 0; j <= n; j++ { //O(n)
		sum += j
	}

	for k := 0; k <= n; k++ { //O(n²)
		for h := 0; h <= n; h++ {
			sum = sum + k*h
		}
	}
}

如上代码中,第一个循环循环了1000次,数量级是常数暂且不理(时间复杂度表示算法执行效率与数据规模增长的变化趋势,不管常量的执行时间多大,都可以把它忽略掉);第二个循环中是一个未知的n,它的时间复杂度就是O(n);第三个循环中是嵌套,即O(n²),而显然O(n²) > O(n),也就是第三个循环的数量级最大,那么这段代码的时间复杂度就是O(n²)

时间复杂度是O(log n)的情况

func demo3() {
	num := 1
	for num < n {
		num = num * 2	// 执行次数最多,需要计算出这行代码被执行了多少次
	}
	fmt.Println(num)
	// 
	// 也就是说循环了x次,即2的x次方=n,所以x=log n,底数2可以忽略
}

以上代码中,num每循环一次就乘以2,它是一个比值为2的等比数列。

它的值列出来就是1*2,1*2*2,1*2*2*2......,即2º,2¹,2²,2³......一直到2的x次方小于n时终止循环,换句话说它将执行x次,即2的x次方=n,那么我们把它的x值求出来就是x=log n,底数2可以忽略(底数为任何常量时都可以忽略,log2n 和log20n是一个复杂度)。

时间复杂度是O(m+n)、O(m*n)的情况

代码就不列了(两个一层循环),为什么不用n表示却用了m和n两个变量,因为这两个值的是无法确定规模的。此时就不能简单的以量级最大的那段复杂度来作为整体的复杂度了,所以这时候就是O(m+n)

当然O(m*n)和上面解释相同,只是O(m*n)是循环嵌套,这时候它的执行次数就是m*n,所以复杂度就是O(m*n)。

空间复杂度

func demo4(){
	arr1 := [1]int{9}
	arr2 := [n]int{}
}

代码中有两个数组,arr1申请了一个空间存储,它的空间复杂度就是O(1),arr2的空间复杂度就是O(n)。

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

func demo5(arr []int, x int) int {
	num := 99
	res := -1
	for key, value := range arr {
		if num == value{
			res = key
			break
		}
	}
	return res
}

看这段代码,遍历传入的数组,找到值为99的元素并返回它的key,找不到则返回-1。

最好的情况就是99这个值正好是arr的第一个元素,这样的话只遍历一次就够了(O(1)),最差的当然是该值在最后面了(O(n)),显然不同情况下时间复杂度是不一样的。

平均时间复杂度

在数组arr中,它的下标是 0~n-1,res的值是多少取决于99这个值在arr中和不在arr中,即一共是n+1种情况:

只1次就找到需要遍历1次,找两次就找到需要遍历2次,全遍历完找到需要n次,不在数组中时需要遍历n次(只有遍历完才能知道),所以执行的次数为:1+2+3+...+n+n/n+1  =   n(n+3)/2(n+1)

进一步简化,去掉常数:

n²+3n/2n  --->   n+3/2   ---->   n

得到的复杂度就是O(n)。假如把每种情况发生的概率也考虑进去:

假设在数组中与不在数组中的概率都为 1/2,要查找的数据出现在 0~n-1 中任意位置的概率就是 1/(2n),那么,

1* 1/(2n)  + 2* 1/(2n) +  n* 1/(2n)  +  n * 1/2  =  (3n+1)/4,这个值就是加权平均值,进行简化,结果还是n,此时的平均时间复杂度就是O(n)。

很多时候用一个复杂度就可以了,时间复杂度如果有量级的差距的话才会细分。

 

黄昏时偷来你的肋骨酿酒  百年后醉得有血有肉

发布了155 篇原创文章 · 获赞 74 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/HYZX_9987/article/details/103869969