数据结构——算法复杂度
一、数据结构定义
数据结构(Data Structure)是计算机存储、组织数据的⽅式,指相互之间存在⼀种或多种特定关系的数据元素的集合。没有⼀种单⼀的数据结构对所有⽤途都有⽤,所以我们要学各式各样的数据结构,如:线性表、树、图、哈希等。
算法:
算法(Algorithm):就是定义良好的计算过程,他取⼀个或⼀组的值为输⼊,并产⽣出⼀个或⼀组值作为输出。简单来说算法就是⼀系列的计算步骤,⽤来将输⼊数据转化成输出结果。
二、算法效率
算法效率是衡量一个算法在解决问题过程中性能表现的关键指标,主要体现在时间效率和空间效率这两个核心方面。
1.复杂度的概念:
算法在编写成可执⾏程序后,运⾏时需要耗费时间资源和空间(内存)资源 。因此衡量⼀个算法的好
坏,⼀般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度主要衡量⼀个算法的运⾏快慢,⽽空间复杂度主要衡量⼀个算法运⾏所需要的额外空间。
2.复杂度的重要性:评估算法性能:准确衡量效率差异 预测算法扩展性 指导算法优化:确定优化方向 对比优化效果 合理选择算法:适配应用场景 平衡资源利用。
三、时间复杂度
定义:在计算机科学中,算法的时间复杂度是⼀个函数式T(N),它定量描述了该算法的运⾏时间。
通常使用大 O 表示法(Big - O notation)来描述时间复杂度。
1.大O的渐进表示法
⼤O符号(Big O notation):是⽤于描述函数渐进⾏为的数学符号
推导⼤O阶规则
1. 时间复杂度函数式T(N)中,只保留最⾼阶项,去掉那些低阶项,因为当N不断变⼤时,
低阶项对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
2. 如果最⾼阶项存在且不是1,则去除这个项⽬的常数系数,因为当N不断变⼤,这个系数
对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
3. T(N)中如果没有N相关的项⽬,只有常数项,⽤常数1取代所有加法常数。
通过以上⽅法,可以得到 Func1 的时间复杂度为: O ( N 2 )。
示例:
// 计算 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);
}
Func2执⾏的基本操作次数:
T (N) = 2N + 10
根据推导规则第3条得出
Func2的时间复杂度为: O(N)
// 计算 Func4 的时间复杂度?
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
Func4执⾏的基本操作次数:
T (N) = 100
根据推导规则第1条得出
Func2的时间复杂度为: O(1)
总结
通过上⾯我们会发现,有些算法的时间复杂度存在最好、平均和最坏情况。
最坏情况:任意输⼊规模的最⼤运⾏次数(上界)。
平均情况:任意输⼊规模的期望运⾏次数。
最好情况:任意输⼊规模的最⼩运⾏次数(下界)。
⼤O的渐进表⽰法在实际中⼀般情况关注的是算法的上界,也就是最坏运⾏情况。
四、空间复杂度
空间复杂度也是⼀个数学表达式,是对⼀个算法在运⾏过程中因为算法的需要额外临时开辟的空间。空间复杂度计算规则基本跟实践复杂度类似,也使⽤⼤O渐进表⽰法。
注意:函数运⾏时所需要的栈空间(存储参数、局部变量、⼀些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运⾏时候显式申请的额外空间来确定。
示例:
// 计算 BubbleSort 的时间复杂度?
void BubbleSort(int* a, int n)
{
assert(a);
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]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
函数栈帧在编译期间已经确定好了,
只需要关注函数在运⾏时额外申请的
空间。
BubbleSort额外申请的空间有
exchange等有限个局部变量,使⽤了
常数个额外空间
因此空间复杂度为 O(1)
// 计算阶乘递归 Fac 的空间复杂度?
long long Fac(size_t N)
{
if(N == 0)
return 1;
return Fac(N-1)*N;
}
Fac递归调⽤了N次,额外开辟了N个函数栈帧,
每个栈帧使⽤了常数个空间
因此空间复杂度为: O(N)
五、常见复杂度对比


总结
在实际应用中,常常需要在时间复杂度和空间复杂度之间进行权衡。有时候为了提高时间效率,可以适当牺牲空间,比如利用缓存机制增加空间占用以加快数据访问速度;而在空间资源受限的场景下,可能会选择空间复杂度较低但时间复杂度稍高些的算法,确保系统能正常运行。 总之,要依据具体的应用场景、资源条件以及对时间和空间的侧重要求等来综合考虑,选择最优的算法或者对现有算法进行合理的优化,以达到最佳的性能表现。