算法复杂度究竟是什么


1. 算法复杂度

1.1 算法复杂度概念

算法,顾名思义就是计算方法,是输入和输出的中间商。算法复杂度,就是用来衡量算法优劣的。

1.2 记法

算法复杂度常用大 O O O标识法记录, O O O表示数量级, O ( f ( n ) ) O(f(n)) O(f(n))表示取问题规模为 n n n的数量计算式子的数量级,这个数量级直接决定当 n n n增长时,算法以什么样的数量级增长。通常常数都化为 1 1 1,通常有下面通识:

  • O ( 3 ) = O ( 1 ) O(3)=O(1) O(3)=O(1) 常量变为 1 1 1
  • O ( 2 n ) = O ( n ) O(2n)=O(n) O(2n)=O(n) 系数变为 1 1 1
  • O ( n 2 + n ) = O ( n ) O(n^2 + n)=O(n) O(n2+n)=O(n) 取幂次最高的
1.3 算法复杂度包含

世界无非就是时间空间,算法也不例外,关系到时间和空间两个维度。算法复杂度,大方面来看分为时间复杂度空间复杂度

2. 时间复杂度

2.1 定义

是算法运行所消耗的时间多少的度量,用 T ( n ) T(n) T(n)表示,有

T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n))

2.2 包含类型
2.2.1 最好情况时间复杂度

是在最理想情况下的时间复杂度,可以说是花时间最少的。例如冒泡排序,如果本身就是已经排好序的了,走第一个冒泡,算法就可以完成了。

2.2.2 最坏情况时间复杂度

是最坏情况下的时间复杂度,可以说是花时间最多的。例如冒泡排序,如果本身是倒序排好的,则算法将走到最后一个冒泡才能完成。

2.2.3 平均情况时间复杂度

这种情况考虑了各种情况的概率,然后计算每种情况的次数,按权重取个平均值。例如,要在n个不同数中找到某一个数,显然这个数在每个位置的概率一样为 1 n \dfrac{1}{n} n1,在第一个位置只需要检查一次,在第二个位置需要检查两次,依次类推,我们计算出平均次数如下:

T ( n ) ‾ = 1 n × 1 + 1 n × 2 + . . . + 1 n × n = 1 n × ( 1 + 2 + . . . + n ) = n + 1 2 \overline{T(n)}=\dfrac{1}{n}\times1+\dfrac{1}{n}\times2+...+\dfrac{1}{n}\times{n}=\dfrac{1}{n}\times(1+2+...+n)=\dfrac{n+1}{2} T(n)=n1×1+n1×2+...+n1×n=n1×(1+2+...+n)=2n+1

从而可知道该算法平均情况复杂度为 O ( n ) O(n) O(n)

2.2.4 均摊时间复杂度

这种复杂度使用场景较为特殊,比如对一个数据进行连续操作,在大多数情况下复杂度较低,特殊情况下复杂度较高,而这组操作存在前后连贯的时序关系。我们将这组操作放在一起,将复杂度高的均摊到复杂度低的上面。一般均摊时间复杂度等于最好情况时间复杂度。

2.3 常见时间复杂度
2.3.1 常数阶 O ( 1 ) O(1) O(1)
public int sum(int i, int j) {
	i++;
	j++;
	return i + j;
}

可以看到这个求和算法经过了若干步就返回了,并没有循环等复杂结构,经过常数步骤后便完成,所以该算法为常数阶。

2.3.2 对数阶 O ( l o g n ) O(log_{}{n}) O(logn)
public int getStep(int n) {
	int i = 1;
	while (i < n) {
		i = i * 2;
	}
	return i;
}

这个可以看成是从步长 1 1 1开始,每一步是原来步长的两倍,知道这个步长超过给定数。我们可以看到也就是:假定经过 x x x步,要达到 2 x > = n 2^x >= n 2x>=n,也就是 x > = l o g 2 n x>=log_{2}{n} x>=log2n,即 x = ⌈ l o g 2 n ⌉ x=\lceil log_{2}{n} \rceil x=log2n,所以该算法为对数阶,注意这种算法底数可以为 2 2 2,也可以为其他正整数,统称对数阶 O ( l o g n ) O(log_{}{n}) O(logn)

2.3.3 线性阶 O ( n ) O(n) O(n)
public int[] getArray(int n) {
	int[] result = new int[n];
	for (int i = 0; i < n; ++i) {
		result[i] = i;
	}
	return result;
}

这个算法可以看到该算法经过了一个循环,次数为 n n n,故为线性阶。

2.3.4 线性对数阶 O ( n l o g n ) O(nlog_{}{n}) O(nlogn)
public void doGetStep(int n) {
	for (int m = 0; m < n; m++) {
		int i = 1;
		while (i < n) {
			i = i * 2;
        }
	}
}

通过上面的对数阶分析我们知道,我们执行 n n n次对数阶,当然执行次数就为 O ( n l o g n ) O(nlog_{}{n}) O(nlogn),为线性对数阶。

2.3.5 平方阶 O ( n 2 ) O(n^2) O(n2)
public int[][] getArrays(int n) {
	int[][] result = new int[n][n];
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			result[i][j] = i * j;
		}
	}
	return result;
}

这个算法可以看到该算法经过了两个循环,每个循环次数为 n n n,总体次数为 n 2 n^2 n2,故为平方阶。

2.3.6 K方阶 O ( n k ) O(n^k) O(nk)

通过以上分析不难发现,经过 n n n个循环,每次为 n n n,则算法时间复杂度就为 O ( n k ) O(n^k) O(nk)

2.4 不常见时间复杂度
2.4.1 指数阶 O ( n k ) O(n^k) O(nk)
public int fibonacci(int n) {
	if (n <= 1) {
		return n;
	}
	return fibonacci(n - 1) + fibonacci(n - 2);
}

我们来看这个问题,这是著名的fibonacci问题的递归解法,网上通用的时间复杂度是 O ( 2 n ) O(2^n) O(2n),是通过树形来证明的,但是有些不严谨,因为有些叶子节点是不足够的,正确时间复杂度应该是 O ( ( 1 + 5 2 ) n ) O((\dfrac{1+\sqrt{5}}{2})^n) O((21+5 )n),有比较简明的证明方法,这里不过多介绍。

2.4.2 阶乘阶 O ( n ! ) O(n!) O(n!)
public void doFactorial(int n) {
	if (n == 1) {
		return;
	}
	for (int i = 0; i < n; i++) {
		doFactorial(n - 1);
	}
}

我们看这个算法,第一循环执行 n n n次,第二循环 n − 1 n-1 n1次,得到次数为 n × ( n − 1 ) × . . . × 2 × 1 n\times(n-1)\times...\times2\times1 n×(n1)×...×2×1,即算法复杂度为 O ( n ! ) O(n!) O(n!),这种算法不常用,不多讲解。

3. 空间复杂度

3.1 定义

是算法运行所占用的存储空间大小的量度,用 S ( n ) S(n) S(n)表示,有:

S ( n ) = O ( f ( n ) ) S(n)=O(f(n)) S(n)=O(f(n))

该存储空间包含:

  • 算法程序所占的存储空间
  • 初始数据所占的存储空间
  • 执行过程中所需要的额外空间
3.2 常见空间复杂度
3.2.1 常数阶 O ( 1 ) O(1) O(1)
public int sum(int n) {
	int total = 0;
	for (int i = 0; i < n; i++) {
		total += i;
	}
	return total;
}

可以看到该算法用到了ntotali这几个变量,不随规模变化而改变,因此空间复杂度为一个常量,因此该空间复杂度属于常数阶。

3.2.2 线性阶 O ( n ) O(n) O(n)
public int[] getArray(int n) {
	int[] result = new int[n];
	for (int i = 0; i < n; ++i) {
		result[i] = i;
	}
	return result;
}

注意到该算法用了nresulti这几个变量,总数为 n + 2 n+2 n+2,因此该空间复杂度属于线性阶。

3.2.1 平方阶 O ( n 2 ) O(n^2) O(n2)
public int[][] getArrays(int n) {
	int[][] result = new int[n][n];
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			result[i][j] = i * j;
		}
	}
	return result;
}

注意到用了nresulti这几个变量,总数为 n 2 + 2 n^2 + 2 n2+2,因此该空间复杂度属于平方阶。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值