目录:
前言
在C语言阶段,我们学过了一些排序和查找算法, 冒泡排序、快排qsort(),二分查找等等,哪种算法更好呢?我们如何衡量一个算法的好坏呢?本文来学习算法的时间复杂度和空间复杂度,相信学完后你就会明白了。
1、 算法效率
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。
随着计算机行业的高速发展,计算机储存容量已经达到了很高的程序,已经不需要特别关注一个算法的空间效率了,重点关注其时间复杂度。
2、 时间复杂度的计算
2.1、 什么是时间复杂度
算法中的基本操作的==执行次数==,为算法的时间复杂度。
直接上实例来讲解具体的计算方法吧
//计算Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
++count;
}
}
for (int k = 0; k < 2 * N; ++k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
算法执行次数函数表达式:F(N) = N^2 + 2 * N + 10
N = 10,F(N) = 130;
N = 100,F(N) = 10210;
N = 1000,F(N) = 1002010;
通过计算发现,N越大,对结果影响越小,所以在实际计算时间复杂度时,我们并不需要计算精确的执行次数,而只需要计算大概执行次数,使用大O渐进表示法(估算),只保留对结果影响最大的一项。
2.2、 大O渐进表示法(估算)
(1)、 推导大O阶方法:
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
来举几个例子说明一下具体是怎么用的,比如:
- 执行次数函数 F(N) = 10,使用大O渐进表示法后,时间复杂度为:O(1)
- 执行次数函数 F(N) = N^2 + 2 * N + 10,使用大O渐进表示法后,时间复杂度为:O(N2)
- 执行次数函数 F(N) = 2 * N + 10,使用大O渐进表示法后,时间复杂度为:O(N)
(2)、有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为 N 数组中搜索一个数据 X
最好情况:1次就找到
最坏情况:N次才找到(一般以最坏情况为准)
平均情况:N/2次找到
而在实际中一般情况关注的是算法的最坏运行情况,是一种保底思维,没有比这更差了哈哈,所以数组中搜索数据时间复杂度为O(N)
2.3、 时间复杂度计算实例
实例1:
// 计算Func2的时间复杂度
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N; ++k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
执行次数函数 F(N) = 2 * N + 10,使用大O渐进表示法后,时间复杂度为O(N)
2.4、 时间复杂度的总结
时间复杂度是衡量算法执行时间随输入规模增长而变化的一种度量方式。它描述了算法在最坏情况下的运行时间增长率。时间复杂度通常用大O符号(O)表示,表示算法运行时间的上限。
3、 空间复杂度的计算
空间复杂度是衡量算法在执行过程中所需存储空间随输入规模增长而变化的一种度量方式。它描述了算法在最坏情况下所需的额外空间。空间复杂度通常用大O符号(O)表示,表示算法所需空间的上线。
3.1、 常见的空间复杂度
- O(1):常数空间复杂度。算法所需的额外空间是固定的,不随输入规模变化。例如,交换两个变量的值。
- O(n):线性空间复杂度。算法所需的额外空间随输入规模线性增长。例如,动态数组。
- O(n^2):平方空间复杂度。算法所需的额外空间随输入规模的平方增长。例如,二维数组。
- O(log n):对数空间复杂度。算法所需的额外空间随输入规模的对数增长。例如,递归调用栈。
3.2、 空间复杂度的计算
空间复杂度的计算通常基于算法所需的额外存储空间。以下是一些常见的计算方法:
- 变量:如果算法只使用固定数量的变量,那么它的空间复杂度是
O(1)
。- 数组:如果算法使用一个大小为
n
的数组,那么它的空间复杂度是O(n)
。- 递归:递归算法的空间复杂度通常与递归深度有关。例如,斐波那契数列的递归实现的空间复杂度是
O(n)
。
3.3、 空间复杂度的总结
空间复杂度是评估算法内存使用情况的重要指标。通常,我们希望选择空间复杂度较低的算法来解决问题。以下是一些总结:
- 常数空间复杂度 O(1):最理想的情况,算法所需的额外空间不随输入规模变化。
- 线性空间复杂度 O(n):适用于需要动态分配空间的算法。
- 平方空间复杂度 O(n^2):适用于需要二维数组的算法,但在输入规模较大时内存消耗较大。
- 对数空间复杂度 O(log n):适用于递归调用栈,通常是高效的。
4、 常见复杂度对比
以下是常见时间复杂度和空间复杂度的对比表,展示了它们随输入规模增长的变化趋势:
复杂度 | 时间复杂度 | 空间复杂度 | 描述 |
---|---|---|---|
O(1) | 常数时间 | 常数空间 | 最理想的情况,执行时间和额外空间都不随输入规模变化。 |
O(log n) | 对数时间 | 对数空间 | 非常高效,适用于快速查找和递归调用栈。 |
O(n) | 线性时间 | 线性空间 | 适用于遍历输入数据和动态数组。 |
O(n log n) | 线性对数时间 | 线性对数空间 | 适用于大多数高效的排序算法。 |
O(n^2) | 平方时间 | 平方空间 | 适用于简单的排序算法和二维数组,但在输入规模较大时效率和内存消耗较高。 |
O(n^3) | 立方时间 | 立方空间 | 适用于一些矩阵运算,但在输入规模较大时效率和内存消耗较高。 |
O(2^n) | 指数时间 | 指数空间 | 适用于一些组合问题,但在输入规模较大时效率极低。 |
O(n!) | 阶乘时间 | 阶乘空间 | 适用于一些排列问题,但在输入规模较大时效率极低。 |
总结
时间复杂度和空间复杂度是评估算法效率和内存使用情况的重要指标。通过理解不同复杂度的含义和计算方法,我们可以选择合适的算法来解决问题,并在输入规模较大时保持高效的性能和合理的内存使用。