草庐IT

【数据结构与算法篇】时间复杂度与空间复杂度

花 碟 2024-01-06 原文

  

目录

一、数据结构和算法

1.什么是数据结构? 

2.什么是算法?

3.数据结构和算法的重要性

二、算法的时间复杂度和空间复杂度

1.算法效率

2.算法的复杂度

3.复杂度在校招中的考察

4.时间复杂度

5.空间复杂度 

6.常见复杂度对比

7.复杂度的OJ练习


 

👻内容专栏:《数据结构与算法》

🐨本文概括: 讲解数据结构和算法的概念、时间复杂度、空间复杂度、常见复杂度对比。

🐼本文作者:花 碟

🐸发布时间:2023.4.13

一、数据结构和算法

1.什么是数据结构? 

数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。 就是说方便在内存中管理数据,进行增删查改的操作。

2.什么是算法?

算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

3.数据结构和算法的重要性

目前校园招聘笔试一般采用Online Judge形式, 一般都是20-30道选择题+2道编程题,或者3-4道编程题。 

 

 

可以看出,现在公司对学生代码能力的要求是越来越高了,大厂笔试中几乎全是算法题而且难度大,中小长的笔试中才会有算法题。算法不仅笔试中考察,面试中面试官基本都会让现场写代码。而算法能力短期内无法快速提高了,至少需要持续半年以上算法训练积累,否则真正校招时笔试会很艰难,因此算法要早早准备。

数据结构与算法对一个程序员来说的重要性? 👈这篇文章是知乎一篇博主对于数据结构与算法的详细介绍,感兴趣的小伙伴们可以看看。

二、算法的时间复杂度和空间复杂度

1.算法效率

如何衡量一个算法的好坏呢?比如对于以下斐波那契数列

long long Fib(int N)
{
    if(N < 3)
        return 1;

    return Fib(N-1) + Fib(N-2);
}

斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?

2.算法的复杂度

算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度

3.复杂度在校招中的考察

4.时间复杂度

👇1.时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数(函数表达式),它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度

即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

// 请计算一下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);
}

👻👻计算一下Func1执行的基本操作次数是多少?

👉计算Func1执行的基本操作次数(函数表达式): F(N) = N² + 2 * N + 10
我们接下来给予一些值进行计算F(N)与N的关系
当N = 10  F(N) = 130

当N = 100  F(N) = 10210

当N = 1000  F(N) = 1002010

当N = 10000 F(N) = 100020010

结论:我们发现随着N的增大,2*N + 10的结果对F(N)的整体结果影响会越来越小,其主要影响的一项是

所以,实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要估算大概执行次,计算出一个量级就行。那么这里我们使用大O的渐进表示法

👇2.大O的渐近表示法

👉大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
使用大O的渐进表示法以后,Func1的时间复杂度为O(N²)

另外有些算法的时间复杂度存在最好、平均和最坏情况
🎍最坏情况:任意输入规模的最大运行次数(上界)
🎋平均情况:任意输入规模的期望运行次数
🎄最好情况:任意输入规模的最小运行次数(下界)
👉例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况需要降低预期,考虑最坏的结果。所以数组中搜索数据时间复杂度为O(N)

👇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);
}

实例1基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)

🎀实例2:

// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
    int count = 0;
    for(int k = 0; k < M; ++ k)
    {
        ++count;
    }
    for(int k = 0; k < N ; ++ k)
    {
        ++count;
    }
    printf("%d\n", count);
}

实例2基本操作执行了M+N次,有两个未知数M和N,无法确定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);
}

实例3基本操作执行了100次,通过推导大O阶方法,时间复杂度为 O(1) 

注⚠️:O(1)不是1次,指的是常数次。

 🎀实例4:

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );

实例4 在字符串中寻找字符。好的情况在第1次就找到了,最坏的情况在尾部找到。基本操作执行最好1次,最坏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;
    }
}

实例5基本操作执行最好N次,即第一趟进去查找已经是有序的数字了,最好情况是O(N)最坏的情况呢,一共需要N - 1趟嘛,从N - 1 趟开始,执行N - 1次、N - 2、N - 3 …… 3、2 、1,可以看出来,这是一个等差数列求和,最坏执行了N*(N-1)/2, 通过推导大O阶方法+时间复杂度一般看最坏,故时间复杂度为 O(N^2)

 🎀实例6:

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
    assert(a);
    int begin = 0;
    int end = n-1;
    //[begin, end]:begin和end是左闭右闭区间,因此有=号
    while(begin <= end)
    {
        int mid = begin + ((end-begin)>>1);
        if (a[mid] < x)
            begin = mid+1;
        else if (a[mid] > x)
            end = mid-1;
        else
            return mid;
    }
   return -1;
}

实例6 基本操作执行最好1次,最坏O(logN)次,时间复杂度为 O(log₂N)。ps:logN在算法分析中表示是底数为2,对数为N。有些地方会写成lgN

 🎀实例7:

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
    if(0 == N)
        return 1;

    return Fac(N-1)*N;
}

实例7 通过计算分析发现基本操作递归了N次,每次调用执行常数次,所以时间复杂度为O(N) 

 🎀实例8:

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
    if(N < 3)
        return 1;

    return Fib(N-1) + Fib(N-2);
}

 画图分析:

实例8 斐波那契数列每一项都是递归调用两个子项我们可以将斐波那契数列的调用过程大致用画图画出来,函数往后调用次数会越来越多,但是到最后几次快调用完了的时候,右边的部分会提前加结束,调用次数会大幅度减少,呈现一个三角形(灰色部分为数据缺失部分),画图不够准确,但大体思想可以这么表示。我们发现,其中调用的每一层是2的倍数,可以看作是一个等比数列,运用错位相减的方法,求出基本操作递归了2^(N - 1) - 1次,故时间复杂度为O(2^N)

5.空间复杂度 

1.空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度
2.空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数
3.空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法
注意⚠️:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显示申请的额外空间来确定。

空间复杂度计算举例 

🎏实例1:

// 计算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;
    }
}

实例1 使用了常数个额外空间,所以空间复杂度为 O(1) 

🎏实例2:

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{
    if(n==0)
        return NULL;

    long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
    fibArray[0] = 0;
    fibArray[1] = 1;

    for(int i = 2; i <= n ; ++i)
    {
        fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
    }
  return fibArray;
}

实例2动态开辟了N个空间,空间复杂度为 O(N)

🎏实例3: 

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
    if(N == 0)
        return 1;

    return Fac(N-1)*N;
}

实例3递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为 O(N) 

6.常见复杂度对比

一般算法常见的复杂度如下:

 

7.复杂度的OJ练习

1.消失的数字 17.04 消失的数字_点击链接跳转

2.轮转数组  189.轮转数组_点击链接跳转

 


🤗🤗 好啦,本篇文章就到此为止啦~ 感谢大家的支持!希望对你有帮助,如有什么疑问,可以在评论区or私信告诉我~~ 🥰🥰😉

有关【数据结构与算法篇】时间复杂度与空间复杂度的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

    我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

  4. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  5. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  6. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  8. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  9. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  10. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

随机推荐