「错位算法时空」,让你彻底学会「时间」与「空间」复杂度
Posted 飞向星的客机
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「错位算法时空」,让你彻底学会「时间」与「空间」复杂度相关的知识,希望对你有一定的参考价值。
⭐ 前言
本篇文章将带领大家深入了解我们 算法的时间复杂度和空间复杂度!
数据结构与算法 的重要性相信不用多说了吧,那么入门之前,必不可少的就是学习我们的 时间复杂度和空间复杂度!
使用不同算法,解决同一个问题,效率可能相差非常大。
为了对算法的好坏进行评价,我们引入 “算法复杂度” 的概念。
Let’s get it!
文章目录
数据结构前言
平时经常在网上看到 数据结构,那么它到底是什么呢?
🎃 什么是数据结构
数据结构(Data Structure):是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
🎃 什么是算法?
算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。
简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。
🎃 数据结构和算法的重要性
这里给大家看一下知乎上是怎么回答的
不言而喻,数据结构与算法 真的非常非常非常非常重要!!!
🎃 如何学好数据结构和算法?
1、死磕代码:光看怎么写,肯定得多去刷题网站上面刷题。(最好从早刷到晚😄)
推荐两个人人皆知的刷题网站:力扣 (LeetCode) 和 牛客网
2、注意画图和思考:很多思路光靠想是不行的,有时候思想抛锚的话,就得需要你画图了。
🎃 书籍及资料推荐
数据结构学习得差不多了,推荐大家都去把 《剑指offer》 和 《程序员代码面试指南》 上的题做一遍
算法效率
🧐思考:如何衡量一个算法的好坏呢?
📃代码示例:斐波那契数列
long long Fib(int N)
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?
所以这就引出了我们算法的复杂度👇
🌳 算法的复杂度
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。
因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
算法效率分析分为两种:
第一种是时间效率
第二种是空间效率。
时间效率被称为时间复杂度,而空间效率被称作空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。
在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。
但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
时间复杂度
🐱🐉 时间复杂度的定义
在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。
一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。
但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。
一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。
即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。
📃先来看一段代码示例:
// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
int count = 0;
//代码一
//两层循环嵌套外循环执行N次,内循环执行N次,整体计算就是N*N的执行次数
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
++count;
//代码二
//2 * N的执行次数
for (int k = 0; k < 2 * N; ++k)
++count;
//代码三
//常数项10
int M = 10;
while (M--)
++count;
printf("%d\\n", count);
🧐分析:
Func1 执行的基本操作次数 :
在代码一中
++count
执行了 N ∗ N N*N N∗N次
在代码二中
++count
执行了N次
在代码三中
++count
执行了10次
所以总共执行次数为 ( N 2 + N + 10 ) (N^2+N+10) (N2+N+10)次。
我们得到一个函数关系: F ( N ) = N 2 + N + 10 F(N)=N^2+N+10 F(N)=N2+N+10
🤔函数关系分析:
上面已经推出了我们的一个函数公式: F ( N ) = N 2 + N + 10 F(N)=N^2+N+10 F(N)=N2+N+10
当N = 10,F(N) = 130
当N = 100,F(N) = 10210
当N = 1000,F(N) = 1002010
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。
由上面的示例引出了我们的大O渐进表示法👇
🐱🐉 大O的渐进表示法
时间复杂度和空间复杂度一般都使用大O的渐进表示法进行表示,大O的渐进表示法规则如下:
1、所有常数都用常数1表示。
2、只保留最高阶项。 如 O ( n 2 + 2 ) O(n^2+2) O(n2+2),保留最高阶项后,成为 O ( n 2 ) O(n^2) O(n2);
3、如果最高阶项存在且不是1,则去除与这个项的系数,得到的结果就是大O阶。 如 O ( 4 n 2 ) O(4n^2) O(4n2),省去最高阶项的系数后,成为 O ( n 2 ) O(n^2) O(n2);
🤔思考:那么我们上面的Func1
函数怎么用大O表示呢?
是不是: O ( N 2 ) + N + 10 O(N^2)+N+10 O(N2)+N+10 呢?
⚠那就大错特错了!!!
使用大O的渐进表示法,要去掉那些对结果影响不大的项,简洁明了的表示出了执行次数
所以正确的应该是: O ( N 2 ) O(N^2) O(N2)
🌰举几个栗子:
1、可以忽略加法常数: O ( 2 n + 3 ) = O ( 2 n ) O(2n + 3) = O(2n) O(2n+3)=O(2n)
2、与最高次项相乘的常数可忽略: O ( 2 n 2 ) = O ( n 2 ) O(2n^2) = O(n^2) O(2n2)=O(n2)
3、最高次项的指数大的,函数随着 n 的增长,结果也会变得增长得更快: O ( n 3 ) > O ( n 2 ) O(n^3) > O(n^2) O(n3)>O(n2)
4、判断一个算法的(时间)效率时,函数中常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数
O ( 2 n 2 ) = O ( n 2 + 3 n + 1 ) O(2n^2) = O(n^2+3n+1) O(2n2)=O(n2+3n+1) 和 O ( n 3 ) > O ( n 2 ) O(n^3) > O(n^2) O(n3)>O(n2)
😎理解了吗?
另外有些算法的时间复杂度存在最好、平均和最坏情况👇
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
🌰举个栗子:
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)
❗❓重点:时间复杂度做的是悲观预期,所以时间复杂度看的是最坏的情况。
🐱🐉 时间复杂度的案例分析
📃案例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);
🤔分析:
第一个循环执行了 2 ∗ N 2*N 2∗N次,第二个循环执行了10次。 总共执行了 2 ∗ N + 10 2 * N+10 2∗N+10次。
因为最高阶项是: 2 ∗ N 2 * N 2∗N,所以时间复杂度是 O ( N ) O(N) O(N)。
📃案例2
计算Func3
的时间复杂度?
void Func3(int N, int M)
int count = 0;
//执行M次
for (int k = 0; k < M; ++k)
++count;
//执行N次
for (int k = 0; k < N; ++k)
++count;
printf("%d\\n", count);
🤔分析:
第一个循环执行了M次,第二个循环执行了N次。 总共执行了$ M+N$次。
最高阶项是M和N,所以时间复杂度是 O ( M + N ) O(M+N) O(M+N)
假设:
M大于N --> O ( M ) O(M) O(M)
N大于M --> O ( N ) O(N) O(N)
M和N一样大 --> O ( M ) / O ( N ) O(M) / O(N) O(M)/O(N)
还是取影响最大的那一项,如果并没有说明M和N的大小关系,那么时间复杂度就是 O ( M + N ) O(M + N) O(M+N)
📃案例3
计算Func4
的时间复杂度?
void Func4(int N)
int count = 0;
for (int k = 0; k < 100; ++k)
++count;
printf("%d\\n", count);
🤔分析:
这个循环执行了100次, 说明总共执行了100次。
最高阶项是100,执行次数为常数次,所以时间复杂度是 O ( 1 ) O(1) O(1)。
📃案例4
计算strchr
的时间复杂度?
const char* strchr(const char* str, int character)
while (*str)
if (*str == character)
return *str;
else
str++;
提示:
strchr
是一个在字符串中查找某个字符的算法
🤔分析:
时间复杂度: O ( N ) O(N) O(N)
在一个字符串中查找一个字符,肯定要变量这个字符串,所以会利用循环,遍历长度次,由于长度是未知的,所以最高阶项N。
最好情况:一次就找到了, O ( 1 ) O(1) O(1)
平均情况: O ( N / 2 ) O(N / 2) O(N/2) , 忽略系数项是 O ( N ) O(N) O(N)
最坏情况:遍历到最后才找到或者字符串中压根就没有, O ( N ) O(N) O(N)
📃案例5
计算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;
🤔分析:
精确的时间复杂度: O
以上是关于「错位算法时空」,让你彻底学会「时间」与「空间」复杂度的主要内容,如果未能解决你的问题,请参考以下文章