系列文章目录
🎈 🎈 我的优快云主页:OTWOL的主页,欢迎!!!👋🏼👋🏼
🎉🎉我的C语言初阶合集:C语言初阶合集,希望能帮到你!!!😍 😍
🔍🔍我的C语言进阶合集:我的C语言进阶合集,期待你的点击!!!🌈🌈
🎉🎉我的数据结构与算法合集:数据结构与算法合集,点进去看看吧!!! 🎊🎊
文章目录
前言
各位博友,大家好!👋 今天为大家送上算法分析中两个核心概念的总结——时间复杂度和空间复杂度🔍。
一、衡量算法的效率
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。
因此衡量一个算法的好坏,一般是从 时间和空间 两个维度来衡量的,即 时间复杂度 和 空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。
二、时间复杂度
(1)时间复杂度的概念
算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。
算法中的 基本操作的执行次数,为算法的时间复杂度。
(2)大O的渐进表示法
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项相乘的常数。
(3)算法的时间复杂度中的最好、平均和最坏情况
最坏情况:任意输入规模的最大运行次数(上界);
平均情况:任意输入规模的平均运行次数;
最好情况:任意输入规模的最小运行次数(下界)。
- 例如:
在一个
长度为 N
的数组中搜索一个数据 x
最好情况:1
次找到
平均情况:N/2
次找到
最坏情况:N
次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为 O(N)
三、空间复杂度
(1)空间复杂度的概念
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
空间复杂度算的是 变量的个数。
空间复杂度计算规则基本跟 时间复杂度 类似,也使用大O渐进表示法。
注意: 函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,
因此空间复杂度主要通过 函数在运行时候显式申请的额外空间 来确定。
四、常见的时间复杂度和空间复杂度分析
实例 1:
#include<stdio.h> // 包含标准输入输出库,用于 printf 函数
void Func() // 定义一个名为 Func 的函数
{
int count = 0; // 初始化一个名为 count 的变量,用于计数
// 循环,k 从 0 迭代到 2倍的 N( N 是一个未定义的常量或宏,需要在其他地方定义)
for (int k = 0; k < 2 * N; ++k)
{
++count; // 每次循环增加 count 的值
}
int M = 10; // 初始化一个名为 M 的变量,并赋值为 10
while (M--) // 当 M 大于 0 时,执行循环体,每次循环后 M 减 1
{
++count; // 每次循环增加 count 的值
}
printf("%d\n", count); // 打印 count 的值,并换行
}
时间复杂度:
实例 1 基本操作执行了
2N+10
次,通过推导 大O阶方法知道,时间复杂度为O(N)
空间复杂度:
实例 1 使用了常数个额外空间,所以空间复杂度为
O(1)
实例 2:
#include<stdio.h> // 包含标准输入输出库,用于 printf 函数
// 定义一个名为 Func 的函数,接受两个整型参数 N 和 M
void Func(int N, int M)
{
int count = 0; // 初始化一个名为 count 的变量,用于计数
// 第一个循环,k 从 0 迭代到 M-1
for (int k = 0; k < M; ++k)
{
++count; // 每次循环增加 count 的值
}
// 第二个循环,k 从 0 迭代到 N-1
for (int k = 0; k < N; ++k)
{
++count; // 每次循环增加 count 的值
}
printf("%d\n", count); // 打印 count 的值,并换行
}
时间复杂度:
实例 2 基本操作执行了
M+N
次,有两个未知数M和N,时间复杂度为O(N+M)
空间复杂度:
实例 2 使用了常数个额外空间,所以空间复杂度为
O(1)
实例 3:
#include<stdio.h> // 包含标准输入输出库,用于 printf 函数
// 定义一个名为 Func 的函数,接受一个整型参数 N
void Func(int N)
{
int count = 0; // 初始化一个名为 count 的变量,用于计数
// 循环,k 从 0 迭代到 99(共 100 次)
for (int k = 0; k < 100; ++k)
{
++count; // 每次循环增加 count 的值
}
printf("%d\n", count); // 打印 count 的值,并换行
}
时间复杂度:
实例 3 基本操作执行了
100
次,通过推导大O阶方法,时间复杂度为O(1)
空间复杂度:
实例 3 使用了常数个额外空间,所以空间复杂度为
O(1)
实例 4:
#include<stdio.h> // 包含标准输入输出库,用于可能的打印功能
#include<assert.h> // 包含断言库,用于检查程序逻辑的正确性
// 定义一个名为 BubbleSort 的函数,用于对整数数组进行冒泡排序
// 参数 a 是指向整数数组的指针,n 是数组中元素的数量
void BubbleSort(int* a, int n)
{
assert(a); // 断言 a 不为空,如果 a 为空,则触发 assertion failure,程序会终止
// 外层循环,控制排序的轮数,从 n 到 1
for (size_t end = n; end > 0; --end)
{
int exchange = 0; // 用于标记这一轮是否发生了交换
// 内层循环,控制每一轮的比较和交换
for (size_t i = 1; i < end; ++i)
{
if (a[i - 1] > a[i]) // 如果前一个元素大于后一个元素,则需要交换
{
Swap(&a[i - 1], &a[i]); // 调用 Swap 函数交换两个元素
exchange = 1; // 发生了交换,将 exchange 标记为 1
}
}
if (exchange == 0) // 如果这一轮没有发生交换,说明数组已经有序,可以提前结束排序
{
break;
}
}
}
时间复杂度:
最坏情况下(数组完全逆序),内层循环每轮会执行
n-1、n-2、...、1次
,
所以总执行次数是等差数列求和:(n-1) + (n-2) + ... + 1 = n(n-1)/2
。
因此,最坏情况下的时间复杂度为O(n^2)
。
在最好的情况下(数组已经是排序好的),
exchange
始终为0
,因此外层循环只会执行一次
,时间复杂度为O(n)
。
空间复杂度:
实例 4 使用了常数个额外空间,所以空间复杂度为
O(1)
实例 5:
#include<assert.h> // 包含断言库,用于检查程序逻辑的正确性
// 定义一个名为 BinarySearch 的函数,用于在已排序的整数数组中查找特定值 x
// 参数 a 是指向整数数组的指针,n 是数组中元素的数量,x 是要查找的值
int BinarySearch(int* a, int n, int x)
{
assert(a); // 断言 a 不为空,如果 a 为空,则触发 assertion failure,程序会终止
int begin = 0; // 初始化开始索引为 0
int end = n - 1; // 初始化结束索引为数组最后一个元素的索引
// 循环直到 begin 和 end 相遇或交叉
while (begin <= end)
{
int mid = begin + ((end - begin) >> 1); // 计算中间索引,
//使用右移位运算符(右移其实就是将该数字大小除以 2)以获得更均匀的分布。
//等价于 mid = (1/2)(begin + end)
if (a[mid] < x) // 如果中间元素小于 x,则在右半区间继续查找
begin = mid + 1; // 更新开始索引为中间索引的下一个位置
else if (a[mid] > x) // 如果中间元素大于 x,则在左半区间继续查找
end = mid - 1; // 更新结束索引为中间索引的前一个位置
else
return mid; // 如果中间元素等于 x,返回中间索引,表示找到了 x
}
return -1; // 如果循环结束仍未找到 x,返回 -1 表示查找失败
}
时间复杂度:
二分查找算法是基于排序数组的查找算法,它通过 重复地将搜索区间减半 来定位目标值。
对于数组a的长度n
,二分查找的循环次数与对数组进行对数分割的次数成正比。
具体来说,在每次迭代中,查找区间缩小一半。设k
为循环次数,
那么满足以下关系:
2^k = n
取对数得到:k = log(n)
实例 5 基本操作执行最好
1
次,最坏O(logN)
次,时间复杂度为O(logN)
。
补充:logN
在算法分析中表示是底数为 2
,对数为 N
。有些地方会写成 lgN
。
空间复杂度:
实例 5 使用了常数个额外空间,所以空间复杂度为
O(1)
实例 6:
// 定义一个名为 Fac 的函数,用于计算给定非负整数 N 的阶乘
// 参数 N 是 size_t 类型的非负整数,表示要计算阶乘的数
long long Fac(size_t N)
{
// 如果 N 为 0,根据阶乘的定义,0 的阶乘是 1,直接返回 1
if (0 == N)
return 1;
// 如果 N 不为 0,递归调用 Fac 函数计算 (N-1) 的阶乘,并将结果乘以 N
// 这是递归算法的典型应用,通过不断减少问题的规模来解决问题
return Fac(N - 1) * N;
}
时间复杂度:
实例 6 通过计算分析发现基本操作递归了
N
次,时间复杂度为O(N)
。
空间复杂度:
实例 6 递归调用了
N
次,开辟了N
个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)
。
五、常见复杂度对比
1314 | 0(1) | 常数阶 |
---|---|---|
3n+4 | 0(n) | 线性阶 |
3n^2 + 4*n + 5 | 0(n^2) | 平方阶 |
3log n + 5 | 0(log n) | 对数阶 |
3nlog n + 5 | 0(nlog n) | nlog n 阶 |
n^3 + n^2 + 6 | 0(n^3) | 立方阶 |
2^n | 0(2^n) | 指数阶 |
- 图片展示:
END
每天都在学习的路上!
On The Way Of Learning