[数据结构](1)时间复杂度详解

我们在编写一个算法的时候,需要衡量这个算法的好坏。由于程序的运行需要耗费时间和空间,所以我们可以从时间的空间两个维度来衡量。

时间复杂度空间复杂度

现在计算机存储容量的发展已经达到了较高的程度,所以我们更注重算法的时间复杂度。

(一个算法好坏的衡量还有其他维度,要根据具体的环境来判断)

什么是时间复杂度

时间复杂度是一个函数,它定性描述该算法的运行时间。

因为算法运行的时间我们没办法算出来,只有上机测试才能知道,不同的机器运行出来的时间也不一样,所以才需要分析时间复杂度。

算法中的基本操作的执行次数,为算法的时间复杂度。

即找到操作次数关于问题规模n的函数f(n),那么时间复杂度记为 O ( f ( n ) ) O(f(n)) O(f(n))

当输入量n逐渐增大时,时间复杂度的极限情形称为算法的“渐近时间复杂度”。

这说明,大O表示的是一种n趋于无穷大的极限情形。


大O表示法

int fun(int n) {
	int count = 0;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			count++;
		}
	}
	for (int i = 0; i < 2 * n; i++) {
		count++;
	}
	int m = 100;
	while (m--) {
		count++;
	}
	return count;
}

可以简单算出操作单元数随n的变化函数为
f ( n ) = n 2 + 2 n + 100 f(n)=n^2+2n+100 f(n)=n2+2n+100

ncount
10010300
10001002100
10000100020100

可以看到随着n的增大, 2 n + 100 2n+100 2n+100对函数值的影响越来越小, n 2 n^2 n2对数据规模的影响起主导作用。

也就是说,在n趋于无穷大的时候,后两项可以忽略,只要抓大头就可以了。

不同时间复杂度的差异

0

实际上时间复杂度是比较模糊的表示方法(因为忽略了影响较小的项和系数),选用算法也不一定时间复杂度越低越好

如: O ( 20 n 2 ) O(20n^2) O(20n2) O ( n 3 ) O(n^3) O(n3)虽然前者可以表示为 O ( n 2 ) O(n^2) O(n2),次数比后者小。但实际上当 n < 20 n<20 n<20的时候,后者的操作单元数比前者小。

而我们平时说 O ( n 2 ) O(n^2) O(n2)的算法比 O ( n 3 ) O(n^3) O(n3)的好,是在默认了数据量足够大的情况下,系数已经起不到决定性作用的时候才把系数给忽略了。

计算时间复杂度时候只要保留最高项,去掉较低的数量级和常数系数

时间复杂度排行:

O(1)常数阶 < O ( log ⁡ n ) O(\log n) O(logn)对数阶 < O ( n ) O(n) O(n)线性阶 < O ( n 2 ) O(n^2) O(n2)平方阶 < O ( n 3 ) O(n^3) O(n3)立方阶 < O ( 2 n ) O(2^n) O(2n)指数阶


另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为 O ( n ) O(n) O(n)

❔ 为什么对数阶的底可以忽略

假设有两个算法,它们的时间复杂度分别为 O ( log ⁡ 2 n ) O(\log_2n) O(log2n) O ( log ⁡ 10 n ) O(\log_{10}n) O(log10n)

由高中学的换底公式
log ⁡ 2 n = log ⁡ 10 n log ⁡ 10 2 \log_2n=\frac{\log_{10}n}{\log_{10}2} log2n=log102log10n
其中 log ⁡ 10 2 \log_{10}2 log102是个常数可以忽略,也就是说 O ( log ⁡ 2 n ) O(\log_2n) O(log2n)= O ( log ⁡ 10 n ) O(\log_{10}n) O(log10n)

所以底数的影响可以忽略,都可以直接记为 O ( log ⁡ n ) O(\log n) O(logn)

💯练习

求以下时间复杂度:

int fun(int n, int m) {
	int count = 0;
	for (int i = 0; i < n; i++) {
		count++;
	}
	for (int i = 0; i < m; i++) {
		count++;
	}
	return count;
}

时间复杂度为 O ( m + n ) O(m+n) O(m+n)

const char* strchr(const char* str, int character);

这是一个从字符串中搜索字符的库函数,遍历搜索最好1次,最坏n次,时间复杂度取最坏情况 O ( n ) O(n) O(n)

void bubble(int* arr, int sz) {
	int i, j, tmp;
	for (i = 0; i < sz - 1; i++) {
		for (j = 0; j < sz - 1 - i; j++) {
			if (arr[j] > arr[j + 1]) {
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

一个冒泡排序。数据规模n就表示数组大小。最好情况是数组已经排好序,遍历一遍执行n次。最坏是升序(降序)转降序(升序),每一趟的执行次数是上一趟的减1,等差数列求出执行 n ( n + 1 ) 2 \frac{n(n+1)}2 2n(n+1)次。简化最坏的情况得出时间复杂度 O ( n 2 ) O(n^2) O(n2)

int BinarySearch(int* a, int n, int x) {
	int begin = 0;
	int end = n;
	while (begin < end) {
		int mid = begin + ((end - begin) >> 1);
		if (a[mid] < x)
			begin = mid + 1;
		else if (a[mid] > x)
			end = mid;
		else
			return mid;
	}
	return -1;
}

这是一个二分查找。最好情况执行1次

最坏情况也就是找到最后一个

设执行次数为 x x x,数据规模为 n n n,因为每执行一次,数据量就对半减少,可以得出

n ( 1 2 ) x = 1 n(\frac12)^x=1 n(21)x=1

2 x = n 2^x=n 2x=n

x = log ⁡ 2 n x=\log_2n x=log2n

取最坏情况,时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

注意二分查找必须保证数组是有序的。

🙉递归的时间复杂度

递归的时间复杂度 = 递归的次数 * 每次递归中的操作次数

int Fac(int N) {
	if (0 == N)
		return 1;
	return Fac(N - 1) * N;
}

递归了n次,每次进行一个乘法操作。时间复杂度为 O ( n ) O(n) O(n)

int Fib(int N) {
	if (N < 3)
		return 1;
	return Fib(N - 1) + Fib(N - 2);
}

斐波那契数列的递归算法。这个代码虽然简短,但是真的好吗?

如要求第五项

递归过程如下:

1

每一个节点代表递归了一次,n足够大可以看作一棵满二叉树,算出所有节点的数量,取最高项得时间复杂度为 O ( 2 n ) O(2^n) O(2n)

指数阶复杂度非常高,所以不建议这样计算斐波那契数列。

优化:

int Fib(int first, int second, int n) {
    if (n < 3) {
        return 1;
    }
    else if (n == 3) {
        return first + second;
    }
    else {
        return fib(second, first + second, n - 1);
    }
}

其实就是first+second赋值给second,之前的second赋值给first。有递归前进段没有递归返回段,

每递归一次n - 1,所以只递归了n次,时间复杂度 O ( n ) O(n) O(n)

非递归但是同样的原理:

int Fib(int n) {
    int n2 = 1, n1 = 1;
    int temp;
    if (n < 3) {
        return 1;
    }
    n -= 2;
    while (n--) {
        temp = n1 + n2;
        n2 = n1;
        n1 = temp;
    }
    return n1;
}
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值