• 【数据结构】时间复杂度和空间复杂度


    目录

    一.什么是数据结构

    二.什么是算法

    三.算法效率

    四.时间复杂度

    定义:

    大O的渐进表示法

    例题:

    例1:

    例2:

     例3:

     例4:

    例5:

    例6:

    常见时间复杂度

    五.空间复杂度

    定义:

    常见空间复杂度

    例题:

    例1:

    例2:

     例3:

    例4:

    六.常见复杂度对


    一.什么是数据结构

    数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的
    数据元素的集合。
     

    二.什么是算法

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

    三.算法效率

    算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度

    时间复杂度:衡量一个算法的运行快慢

    空间复杂度:衡量一个算法运行需要的额外空间。

    在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
    空间复杂度早期关注,随着电脑内存越来越大,空间复杂度渐渐不被关注

    四.时间复杂度

    定义:

    算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。

    环境不同(机器不同),具体运行时间就不同,时间复杂度不是指时间,而是指算法中的基本操作的执行次数,为算法的时间复杂度

    大O的渐进表示法

    大O符号:是用于描述函数渐进行为的数学符号

    1. 用常数1取代运行时间中的所有加法常熟
    2. 在修改后的运行次数函数中,只保留最高阶项
    3. 如果最高阶项存在且不是1,则去除与这个项目相乘的常熟。得到的结果就是大O的阶

    通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
     

    有时候,我们会发现,一些代码的时间复杂度存在最好、平均和最坏情况:

    最坏情况:任意输入规模的最大运行次数(上界)
    平均情况:任意输入规模的期望运行次数
    最好情况:任意输入规模的最小运行次数(下界)
    在实际情况中一般关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为最坏情况。

    例题:

    例1:

    1. //计算Func1的时间复杂度
    2. void Func1(int N)
    3. {
    4. int count = 0;
    5. for (int k = 0; k < 2 * N; ++k)
    6. {
    7. ++count;
    8. }
    9. int M = 20;
    10. while (M--)
    11. {
    12. ++count;
    13. }
    14. printf("%d\n", count);
    15. }

    时间复杂度为O(N)

    函数内for循环循环了2N次,虽然下面while循环循环了20次但20是常量,不计入时间复杂度。

    而与未知数相乘的常数可以去除,最后为N

    2N+10 --->  2N --->  2N和N随着N的值不断变大,差距越来越小,在巨大数字的面前可以忽略不计

    例2:

    1. //计算Func2的时间复杂度
    2. void Func2(int N,int M)
    3. {
    4. int count = 0;
    5. for (int k = 0; k < M; ++k)
    6. {
    7. ++count;
    8. }
    9. for (int k = 0; k < N; ++k)
    10. {
    11. ++count;
    12. }
    13. printf("%d\n", count);
    14. }

     时间复杂度为O(N+M)

    如果:

    M远大于N  --->  O(M)

    N远大于M  --->  O(N)

    M和N差不多大  --->  O(M)或O(N)

     例3:

    1. //计算下面循环的时间复杂度
    2. while(*str)
    3. {
    4. if(*str == character)
    5. return str;
    6. else
    7. ++str;
    8. }

    str = "Hello word"   (str内的值不唯一)

    查找元素时间复杂度情况
    hO(1)最好情况任意输入规模的最小运行次数(下界)
    w

    O(N/2)

    平均情况:任意输入规模的期望运行次数
    dO(N)最坏情况:任意输入规模的最大运行次数(上限)
    • 当一个算法随着输入不同,时间复杂度不同。
    • 当求一个函数的时间复杂度时,求的是最坏的情况下的时间复杂度

     例4:

    1. // 计算BubbleSort的时间复杂度?
    2. void BubbleSort(int* a, int n)
    3. {
    4. assert(a);
    5. for (size_t end = n; end > 0; --end)
    6. {
    7. int exchange = 0;
    8. for (size_t i = 1; i < end; ++i)
    9. {
    10. if (a[i-1] > a[i])
    11. {
    12. Swap(&a[i-1], &a[i]);
    13. exchange = 1;
    14. }
    15. }
    16. if (exchange == 0)
    17. break;
    18. }
    19. }

    在最外层循环内,需要循环n次,在大循环内还要再循环,第一次循环是n次,第二次是n-1次,依次递减。

     我们要计算时间复杂度,可以看出是将小循环全部相加,那就是等差数列相加。

    为:(n+1)*n/2 = (n^2+n)/2

    根据大O的渐进表达式可知为O(n^2)

    例5:

    1. // 计算阶乘递归Fac的时间复杂度?
    2. long long Fac(size_t N)
    3. {
    4. if(0 == N)
    5. return 1;
    6. return Fac(N-1)*N;
    7. }

    该代码为递归,共进行了N次递归,时间复杂度为O(N)

    例6:

    1. // 计算斐波那契递归Fib的时间复杂度?
    2. long long Fib(size_t N)
    3. {
    4. if(N < 3)
    5. return 1;
    6. return Fib(N-1) + Fib(N-2);
    7. }

    该函数为斐波那契数列,我们可以由图得出它的时间复杂度:

    对于斐波那契数列,我们都知道,越往下,递归可能会在右边断掉,但是对于时间复杂度而言,我们求的是一个模糊的概念,断掉的那部分相对于整体而言是无伤大雅的,我们可以看出这是一个等比数列,可以根据等比数列求和算出为2^(n-1)-1.

    由大O的渐进表达式就可以得出它的时间复杂度为:O(2^n)

    常见时间复杂度

    常熟阶O(1)

    对数阶O(logN)

    线性阶O(n)

    线性对数阶O(nlog2)

    平方阶O(n^{2}

    立方阶O(n^{3})

    k次方阶O(n^{k})

    五.空间复杂度

    定义:

    空间复杂度是:临时占用存储空间大小的量度

    • 空间复杂度不是指程序占用多少空间,这没有意义,空间复杂度算的是函数内创造的变量占用的空间个数
    • 同样使用大O的渐进表示法
    • 函数运算时需要的栈空间(存储空间、局部变量、一些寄存器信息等)在进入函数前已经确认好,不计入空间复杂度,因此空间复杂度主要通过函数在运行时显示申请的额外空间来确定

    常见空间复杂度

    O(1)

    O(n)

    O(n^{2})

    例题:

    例1:

    1. int BinarySearch(int* a, int n, int x)
    2. {
    3. assert(a);//查看指针a是否为空,不影响判断空间复杂度
    4. int begin = 0;
    5. int end = n;
    6. while (begin < end)
    7. {
    8. int mid = begin + ((end - begin) >> 1);
    9. if (a[mid] < x)
    10. begin = mid + 1;
    11. else if (a[mid] > x)
    12. end = mid;
    13. else
    14. return mid;
    15. }
    16. return -1;
    17. }

    空间复杂度O(1)

    只创建了三个变量,为常数,计为O(1)

    例2:

    1. int Fac(int N)
    2. {
    3. if (N == 1)
    4. return 1;
    5. return Fac(N - 1) * N;
    6. }

    空间复杂度O(N)

    每次递归后,创建新的栈区,此函数可以看作创建了n个栈区

     例3:

    1. int* m = (int*)malloc(sizeof(int)*n);
    2. for(i=1; i<=n; ++i)
    3. {
    4. j = i;
    5. j++;
    6. }

    空间复杂度O(N)

    malloc创建了n个新空间

    例4:

    1. // 计算斐波那契递归Fib的时间复杂度?
    2. long long Fib(size_t N)
    3. {
    4. if(N < 3)
    5. return 1;
    6. return Fib(N-1) + Fib(N-2);
    7. }

    这又是一个斐波那契数列,但与时间复杂度不同的是,空间是可以重复利用的。

     递归是先从N开始一直到2,在返回之后在递归。

     当达到空间利用到N-1时,就会销毁空间,返回值,在下次利用空间时,利用的是销毁的空间,这样斐波那契数列的空间复杂度为O(N)

    六.常见复杂度对

    5201314O(1)常数阶
    3n+4O(n)线性阶
    3n^2+4n+5O(n^2)平方阶
    3log(2)n+4O(logn)对数阶
    2n+3nlog(2)n+14O(nlogn)nlogn阶
    n^3+2n^2+4n+6O(n^3)立方阶
    2^nO(2^n)指数阶

  • 相关阅读:
    【uni-app从入门到实战】商品列表
    【Unity3D】缩放、平移、旋转场景
    2022年大一学生实训作业【基于HTML+CSS制作中华传统文化传统美德网站 (6页面)】
    【Flutter组件】路由与导航
    【中国知名企业高管团队】系列49:VIVO
    并发编程的三大特性
    wireshark抓包解密TLS,解决个人环境看不到明文流量
    开关量8入4出,高速以太网通讯Socket自由协议远程IO模块 YJ94
    09 | Harbor中的镜像清除
    Leetcode 第 367 场周赛题解
  • 原文地址:https://blog.csdn.net/m0_52094687/article/details/126696611