数据结构基本概念及算法分析

news2024/11/18 8:46:26

文章目录

  • 1. 数据结构基本概念
    • 1.1 基本概念和术语
      • 1.1.1 数据
      • 1.1.2 数据元素
      • 1.1.3 数据项
      • 1.1.4 数据对象
      • 1.1.5 数据结构
    • 1.2 逻辑结构与物理结构
      • 1.2.1 逻辑结构(我们最需要关注的问题)
      • 1.2.2 物理机构
    • 1.3 数据类型
    • 1.3.1 数据类型定义
    • 1.3.2 抽象数据类型
  • 2. 算法分析
    • 2.1 算法的复杂度
    • 2.2 复杂度的渐进表示法
    • 2.3 时间复杂度
      • 2.3.1 一个简单的例子
      • 2.3.2 一般法则
      • 2.3.3 其他一些例子
    • 2.4 空间复杂度
      • 2.4.1 一些例子
    • 2.5 常见复杂度

  • 1. 数据结构基本概念
    • 1.1 基本概念和术语
      • 1.1.1 数据
      • 1.1.2 数据元素
      • 1.1.3 数据项
      • 1.1.4 数据对象
      • 1.1.5 数据结构
    • 1.2 逻辑结构与物理结构
      • 1.2.1 逻辑结构(我们最需要关注的问题)
      • 1.2.2 物理结构
    • 1.3 数据类型
    • 1.3.1 数据类型定义
    • 1.3.2 抽象数据类型
  • 2. 算法分析
    • 2.1 算法的复杂度
    • 2.2 复杂度的渐进表示法
    • 2.3 时间复杂度
      • 2.3.1 一个简单的例子
      • 2.3.2 一般法则
      • 2.3.3 其他一些例子
    • 2.4 空间复杂度
      • 2.4.1 一些例子
    • 2.5 常见复杂度

1. 数据结构基本概念

数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科

1.1 基本概念和术语

1.1.1 数据

数据: 描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合.

数据不仅包括整型,实型等数据类型,还包括字符及声音,图像,视频等非数值类型.

例如:
网页,图片,音频,视频等

在这里插入图片描述
再比如学校学习的各种知识
在这里插入图片描述

对于计算机来说,数据就是符号,这些符号必须具有两个前提:

  • 可以输入到计算机中
  • 能被计算机程序处理

对于整型,实型等数据类型,可以进行数值计算
对于字符数据类型,就需要进行二进制编码来存储.

1.1.2 数据元素

数据元素: 是组成数据的,有一定意义的基本单位,在计算机中通常作为整体处理,也被称为记录

例如:
人类中,人是数据元素
在这里插入图片描述

畜禽类,牛,马,羊等动物是畜禽类的数据元素
在这里插入图片描述

1.1.3 数据项

数据项: 一个数据元素可以由若干个数据项组成

例如:
一个人有眼睛,耳朵,鼻子,嘴巴等数据项;也有姓名,年龄,性别,家庭地址等数据项
在这里插入图片描述

数据项是数据不可分割的最小单位.

但真正讨论问题,数据元素才是数据结构中建立数据模型的着力点.就比如通讯录管理系统中,构建人信息的顺序表时,是将人这个数据元素作为着力点,而不会关心具体的年龄等数据项的.

1.1.4 数据对象

数据对象: 是性质相同的数据元素的集合,是数据的子集

对于有眼睛,耳朵,鼻子,嘴巴的人这个数据元素组成的数据对象,和对于有姓名,年龄,性别,家庭地址的人的这个数据元素组成的数据对象,两者是不同的数据对象.

1.1.5 数据结构

是相互之间存在一种或多种特定关系的数据元素的集合

计算机中,数据元素并不是孤立,杂乱无序的,而是具有内在联系的数据集合.数据元素之间存在的一种或多种特定关系,也就是数据的组织形式.
在这里插入图片描述

1.2 逻辑结构与物理结构

数据结构分为 逻辑结构 和 物理结构

1.2.1 逻辑结构(我们最需要关注的问题)

逻辑结构: 是指数据对象中数据元素之间的相互关系.

  1. 集合结构

集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系

在这里插入图片描述

  1. 线性结构

线性结构中的数据元素之间是一对一的关系

在这里插入图片描述

  1. 树形结构

树形结构中的数据元素之间存在一种一对多的层次关系

在这里插入图片描述

  1. 图形结构

图形结构中的数据元素是多对多的关系

在这里插入图片描述

每个数据元素用圆圈表示,元素之间的逻辑关系用结点之间的连线表示,如果这个关系是有方向的,那么用带箭头的连线表示.

在问题理解的基础上,选择一个合适的数据结构表示数据之间的逻辑关系.

1.2.2 物理机构

物理结构: 是指数据的逻辑结构在计算机中的存储形式

  1. 顺序存储结构

是把数据元素存放在地址里连续的存储单元里, 其数据间的逻辑关系和物理关系是一致的

例如C语言中的数组就是顺序存储结构的
在这里插入图片描述

  1. 链式存储结构

是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的.

例如单链表就是链式存储结构的

在这里插入图片描述

1.3 数据类型

数据类型: 是指一组性质相同的值的集合及定义在此集合上的一些操作的总称

1.3.1 数据类型定义

C语言中,按照取值的不同,数据类型可以分为两类:

  1. 原子类型
    是不可以再分解的基本类型,包括整型,实型,字符型等等
  2. 结构类型
    有若干个类型组合而成,是可以再分解的.例如,整型数组是由若干个整型数据组成的.

1.3.2 抽象数据类型

抽象数据类型(Abstract Data Type, ADT): 一个数学模型及定义在该模型上的一组操作

描述抽象数据类型的标准格式

在这里插入图片描述

2. 算法分析

算法(alorithm)是为求解一个问题需要遵循的,被清楚地指定的简单指令的集合.

对于一个问题,一旦给定某种算法并且(以某种方式)确定其是正确的,那么重要的一部就是确定该算法将需要多少诸如时间或者空间等资源量的问题.

对于以下斐波那契数列:

long long Fib(int N)
{
    if (N < 3)
        return 1;
    
    return Fib(N-1) + Fib(N-2);
}

斐波那契数列的递归实现方式非常简洁,但是这种写法并不好.就需要从时间或者空间来衡量其好坏.

2.1 算法的复杂度

算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源.

因此,衡量一个算法的好坏,一般是从时间空间两个维度来衡量的,即 时间复杂度空间复杂度

  • 时间复杂度主要衡量一个算法的运行快慢
  • 空间复杂度主要衡量一个算法运行所需要的额外空间.

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

在这里插入图片描述

具体可以详细了解以下摩尔定律对于芯片领域的影响

2.2 复杂度的渐进表示法

在这里插入图片描述

这些定义的目的是在函数间建立一种相对的级别.在算法分析中,比较函数间的相对增长率(relative rate of growth)是十分重要的度量.

例如:
T ( N ) = 1000 N , F ( N ) = N 2 T(N) = 1000N,F(N) = N^2 T(N)=1000N,F(N)=N2.

虽然 N 较小时 1000 N 要比 N 2 大 , 但 N 2 以更快的速度增长 , 因此 N 2 最终将更大 . 虽然N较小时1000N要比N^2大,但N^2以更快的速度增长,因此N^2最终将更大. 虽然N较小时1000N要比N2,N2以更快的速度增长,因此N2最终将更大.

在第一个定义中 : n 0 = 1000 而 c = 1 , 也可以令 n 0 = 10 而 c = 100. 在第一个定义中:n_0 = 1000而c =1,也可以令n_0 = 10而c=100. 在第一个定义中:n0=1000c=1,也可以令n0=10c=100.

因此可以说 1000 N = O ( N 2 ) ( N 平方级 ) 因此可以说1000N=O(N^2)(N平方级) 因此可以说1000N=O(N2)(N平方级), 这种记法成为大 O 记法 . 这种记法成为大O记法. 这种记法成为大O记法.


如果用传统的不等式来计算增长率,那么

第一个定义是说 T ( N ) 的增长率小于等于 f ( N ) 的增长率 第一个定义是说T(N)的增长率小于等于f(N)的增长率 第一个定义是说T(N)的增长率小于等于f(N)的增长率

第二个定义是说 T ( N ) 的增长率大于等于 f ( N ) 的增长率 第二个定义是说T(N)的增长率大于等于f(N)的增长率 第二个定义是说T(N)的增长率大于等于f(N)的增长率

第三个定义是说 T ( N ) 的增长率等于 f ( N ) 的增长率 第三个定义是说T(N)的增长率等于f(N)的增长率 第三个定义是说T(N)的增长率等于f(N)的增长率

第四个定义是说 T ( N ) 的增长率小于 f ( N ) 的增长率 第四个定义是说T(N)的增长率小于f(N)的增长率 第四个定义是说T(N)的增长率小于f(N)的增长率


需要掌握的重要结论为:

法则1:如果 T 1 ( N ) = O ( f ( N ) ) 且 T 2 ( N ) = O ( g ( N ) ) T_1(N) = O(f(N))且T_2(N)=O(g(N)) T1(N)=O(f(N))T2(N)=O(g(N)),那么
(a) T 1 ( N ) + T 2 ( N ) = m a x ( O ( f ( N ) ) , O ( g ( N ) ) ) T_1(N)+T_2(N) = max(O(f(N)), O(g(N))) T1(N)+T2(N)=max(O(f(N)),O(g(N)))
(b) T 1 ( N ) ∗ T 2 ( N ) = O ( f ( N ) ∗ g ( N ) ) T_1(N) * T_2(N) = O(f(N) * g(N)) T1(N)T2(N)=O(f(N)g(N))

法则2:如果 T ( N ) T(N) T(N)是一个 k k k次多项式,则 T ( N ) = θ ( N k ) T(N) =\theta(N^k) T(N)=θ(Nk)

法则3:对任意常数 k k k, l o g k N = O ( N ) log^kN=O(N) logkN=O(N).它告诉我们对数增长的非常缓慢.

2.3 时间复杂度

时间复杂的定义: 在计算机科学中, 算法的时间复杂度是一个函数, 它定量描述了该算法的运行时间.

一个算法执行所耗费的时间,从理论上来说,是不能算出来的,只有你把你程序放在机器上跑起来,才能知道.

但是我们需要每个算法都上机测试嘛?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式.

一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度

为了简化分析,忽略特定的时间单元,只要计算大 O O O运行时间.

  1. 抛弃前导常数, 即用常数 1 取代运行时间中的所有加法常数
  2. 抛弃低阶项, 即再修改后的运行次数函数中, 只保留最高阶项

由于大 O O O是一个上界,因此我们必须仔细,绝对不要低估程序的运行时间.

实际上,分析的结果为程序在一定的时间范围内能够终止运行提供了保障.程序可能提前结束,但绝不可能延后.

2.3.1 一个简单的例子

这里是计算 ∑ i = 1 N i 3 \sum_{i=1}^{N}i^3 i=1Ni3 的一个简单的程序片段:

int sum(int N)
{
    int i, PartialSum;

    PartialSum = 0;                 //1
    for (i = 1; i <= N; i++)        //2
        PartialSum += i * i * i;    //3

    return PartialSum;              //4
}

对这个程序的分析很简单.

  1. 声明不计时间
  2. 第 1 行和第 4 行各占一个时间单元.
  3. 第 3 行每执行一次占用 4 个时间单元(两次乘法, 一次加法和一次赋值).执行 N N N 次占用 4 N 4N 4N 个时间单元
  4. 第 2 行在初始化 i i i,测试 i ⩽ N i \leqslant N iN和对 i i i的自增运算中隐含着开销.
    第 2 行的总开销是: 初始化占 1 个时间单元, 所有的测试占 N + 1 N+1 N+1 个时间单元,以及所有的自增运算占 N N N 个时间单元, 共 2 N + 2 2N + 2 2N+2 个时间单元.
  5. 忽略调用函数和返回值的开销,得到的总量是 6 N + 4 6N + 4 6N+4.因此,该函数是 O ( N ) O(N) O(N) 的.

但是如果每次分析一个程序都要演示所有这些工作,这显然是不可行的.
根据大 O O O 的结果,因此就存在许多可以采取的简化并且不影响最后的结果.
例如,第 3 行(每次执行时)显然是 O ( 1 ) O(1) O(1) 语句. 第 1 行与for循环相比显然是不重要的.

由此得到若干一般法则.

2.3.2 一般法则

法则 1 ---- for循环:

一次for循环的运行时间至多是该for循环内语句(包括测试)的运行时间乘以迭代的次数

法则 2 ---- 嵌套的for循环:

从里向外分析这些循环.在一组嵌套循环内部的一条语句总的运行时间为 该语句的运行时间 乘以 该组所有的for循环的大小的乘积.

例如:下列程序片段的运行时间为 O ( N 2 ) O(N^2) O(N2):

for(i = 0; i < N; i++)
    for (j = 0; j < N; j++)
        k++;

法则3 ---- 顺序语句:

将各个语句的运行时间求和即可(这意味着, 其中的最大值就是所得的运行时间, 见2.2 法则1(a))

例如:下面的程序片段先用去 O ( N ) O(N) O(N), 再花费 O ( N 2 ) O(N^2) O(N2), 总的开销也是 O ( N 2 ) O(N^2) O(N2):

for (i = 0; i < N; i++)
    A[i] = 0;
for (i = 0; i < N; i++)
    for (j = 0; j < N; j++)
        A[i] += A[j] + i + j;

法则4 ---- if/else语句:

一个if/else语句的运行时间从不超过判断的时间加上 S 1 S1 S1 S 2 S2 S2 中运行时间较长者的总的运行时间

显然某些情形下这么估计有些过高, 但绝对不会过低.

其他的法则都是显而易见的,但是,分析的基本策略是从内部(或最深层部分)向外展开的.
如果有函数调用,那么这些调用要首先分析.

如果有递归过程,那么存在几种选择.
若递归实际上时稍加演示的for循环,则分析通常是很简单的.
例如下面的函数实际上就是一个简单的循环,从而其运行时间为 O ( N ) O(N) O(N) :

long int Factorial(int N)
{
    if (N <= 1)
        return 1;
    else
        return N * Factorial(N - 1);
}

现在我们重回斐波那契数列,他对递归使用的效率低的令人诧异.

long long Fib(int N)
{
    if (N <= 1)                     //1
        return 1;                   //2    
    else                                
        return Fib(N-1) + Fib(N-2); //3
}

T ( N ) T(N) T(N) 为函数 F i b ( N ) Fib(N) Fib(N) 的运行时间

如果 N = 0 或 N = 1 N = 0 或 N = 1 N=0N=1,则运行时间是某个常数值,即第 1 行做判断以及返回所用的时间.
因为常数并不重要,所以我们可以说 T ( 0 ) = T ( 1 ) = 1 T(0) = T(1) = 1 T(0)=T(1)=1.

N > 2 N > 2 N>2, 则执行该函数的时间是第 1 行上的常数工作加上第 3 行上的工作.
第 3 行由一次加法和两次函数调用组成.由于函数调用不是简单的计算,必须通过它们本身来分析.

2 0 2^0 20      F i b ( N ) Fib(N) Fib(N)
2 1 2^1 21      F i b ( N − 1 ) + F i b ( N − 2 ) Fib(N-1) + Fib(N-2) Fib(N1)+Fib(N2)
2 2 2^2 22      F i b ( N − 2 ) + F i b ( N − 3 ) + F i b ( N − 3 ) + F i b ( N − 4 ) Fib(N-2) + Fib(N-3) + Fib(N-3) + Fib(N-4) Fib(N2)+Fib(N3)+Fib(N3)+Fib(N4)
2 3 2^3 23      F i b ( N − 3 ) + F i b ( N − 4 ) + F i b ( N − 4 ) + F i b ( N − 5 ) + F i b ( N − 4 ) + F i b ( N − 5 ) + F i b ( N − 5 ) + F i b ( N − 6 ) Fib(N-3) + Fib(N-4) + Fib(N-4) + Fib(N-5) + Fib(N-4) + Fib(N-5) + Fib(N-5) + Fib(N-6) Fib(N3)+Fib(N4)+Fib(N4)+Fib(N5)+Fib(N4)+Fib(N5)+Fib(N5)+Fib(N6)


2 N − 1 2^{N-1} 2N1      F i b ( 1 ) + . . . . . Fib(1) + ..... Fib(1)+.....

T ( N ) = 2 0 + 2 1 + 2 2 + . . . + 2 N − 1 − X ( X 指缺的一些调用 , 往高了算 , 可以直接省去 − X ) T(N) = 2^0 + 2^1 + 2^2 +...+ 2^{N-1} - X(X指缺的一些调用, 往高了算, 可以直接省去 - X) T(N)=20+21+22+...+2N1X(X指缺的一些调用,往高了算,可以直接省去X)

T ( N ) = 2 N − 1 T(N) = 2^N - 1 T(N)=2N1

所以该程序是 O ( 2 N ) O(2^N) O(2N) 的,这是指数倍增长.

2.3.3 其他一些例子

实例1: 计算Func1的时间复杂度

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

F ( N ) = N 2 + 2 N + 10 F(N) = N^2 + 2N + 10 F(N)=N2+2N+10 ⇒ \Rightarrow O ( N 2 ) O(N^2) O(N2)


实例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);
}

F ( N ) = 2 N + 10 F(N) = 2N + 10 F(N)=2N+10 ⇒ \Rightarrow O ( N ) O(N) O(N)


实例3 计算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);
}

F ( N ) = N + M F(N) = N + M F(N)=N+M ⇒ \Rightarrow O ( N + M ) O(N + M) O(N+M)

N 远大于 M时, O ( N ) O(N) O(N)
N 远小于 M时, O ( M ) O(M) O(M)
N 和 M 差不多大时, O ( N + M ) O(N + M) O(N+M)


实例4 计算Func4的时间复杂度

void Func4(int N) 
{
    int count = 0;
    for (int k = 0; k < 100; ++ k) 
    {
        ++count;
    }
 
    printf("%d\n", count);
}

F ( N ) = 100 F(N) = 100 F(N)=100 ⇒ \Rightarrow O ( 1 ) O(1) O(1)

常数无论多大都是 O ( 1 ) O(1) O(1)


实例5 计算strchr的时间复杂度

const char * strchr ( const char * str, int character );

最好 1 次, 最坏 N 次,时间复杂度取最坏情况

F ( N ) = N F(N) = N F(N)=N ⇒ \Rightarrow O ( N ) O(N) O(N)


实例6 计算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;
        }
    }
} 

第一轮 n - 1 次, 第二轮 n - 2 次 … 最后一轮 1 次

F ( N ) = N ( N − 1 ) / 2 F(N) = N(N - 1)/2 F(N)=N(N1)/2 ⇒ \Rightarrow O ( N 2 ) O(N^2) O(N2)
在这里插入图片描述


实例7 计算BinarySearch的时间复杂度

int BinarySearch(int* a, int n, int x) 
{
    assert(a);
    int begin = 0;
    int end = n - 1;
    while (begin < end) 
    {
        int mid = begin + ((end-begin) >> 1);
        if (a[mid] < x)
            begin = mid + 1;
        else if (a[mid] > x)
            end = mid;
        else
            return mid;
    }
 
    return -1;
}

若遇到复杂算法, 不能看循环, 要思考其中的思想
在这里插入图片描述

假设一共由 N N N 个数,最坏情况下经过 x x x 次查找,最后只剩一个数,那么 2 x = N 2^x = N 2x=N ⇒ \Rightarrow x = l o g 2 N x = log_2N x=log2N

那么时间复杂度为 O ( l o g N ) O(logN) O(logN)

在数据有序的情况下,二分查找确实效率很快

N 个数中查找大概查找次数
100010
100W20
1亿30

实例7: 计算递归版阶乘的时间复杂度

long long Fac(size_t N)
{
    if (0 == N)
        return 1;
    return Fac(N - 1) * N;
}

递归操作了 N 次

在这里插入图片描述

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

2.4 空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度.

空间复杂度不是程序占用了多少 bytes 的空间,因为这个也没太大意义, 所以空间复杂度算的是变量的个数.空间复杂度也使用 O O O 渐进表示法.

注意 :函数运行时所需要的栈空间(存储参数,局部变量,一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定.

2.4.1 一些例子

实例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;
        }
    }
}

使用了常数个空间(创建了end,exchange,i三个变量)

结果为 O ( 1 ) O(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));  //1
    fibArray[0] = 0;
    fibArray[1] = 1;
    for (int i = 2; i <= n ; ++i) 
    {
        fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
    }
 
    return fibArray;
}

第 1 行开辟了 n + 1 个内存空间

结果为 O ( N ) O(N) O(N)


实例3: 计算递归版阶乘Fac的空间复杂度

long long Fac(size_t N)
{
    if (0 == N)
        return 1;
    return Fac(N - 1) * N;
}

递归调用了 N 次, 开辟了 N 个栈帧空间, 每个栈帧空间使用常数个空间

结果为 O ( N ) O(N) O(N)


实例4: 计算递归版斐波那契数列Fib的空间复杂度

long long Fib(int N)
{
    if (N <= 1)                     //1
        return 1;                   //2    
    else                                
        return Fib(N-1) + Fib(N-2); //3
}

下面是我调用Fib(5),进行调式得到的部分开辟堆栈图
在这里插入图片描述

发现 N 从 Fib(5) -> Fib(2) 一直在开辟栈帧
直到 Fib(1),一次调用返回,开辟的一次空间归还给操作系统,继续调用Fib(0),发现用的还是同一块栈.

空间是重复利用的!

回到题目Fib(N)开辟空间一直到Fib(1),随后释放栈帧返回到Fib(2),继续Fib(0)开辟栈帧;
随后返回Fib(3)…依次类推.最多只开辟了 N 个栈帧, 每个栈帧使用了常数个空间

结果是 O ( N ) O(N) O(N)

由此也能看出递归的最大问题:深度太深,容易栈溢出


时间是累积的,空间重复利用

2.5 常见复杂度

函数式复杂度阶数
5201314 5201314 5201314 O ( 1 ) O(1) O(1)常数阶
3 n + 4 3n+4 3n+4 O ( n ) O(n) O(n)线性阶
3 n 2 + 4 n + 5 3n^2+4n+5 3n2+4n+5 O ( n 2 ) O(n^2) O(n2)平方阶
3 l o n g 2 n + 4 3long_2n+4 3long2n+4 O ( l o g n ) O(logn) O(logn)对数阶
2 n + 3 n l o g 2 n + 14 2n+3nlog_2n+14 2n+3nlog2n+14 O ( n l o g n ) O(nlogn) O(nlogn) n l o g n nlogn nlogn
n 3 + 2 n 2 + 4 n + 6 n^3+2n^2+4n+6 n3+2n2+4n+6 O ( n 3 ) O(n^3) O(n3)立方阶
2 n 2^n 2n O ( 2 n ) O(2^n) O(2n)指数阶

在这里插入图片描述

本章完.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/788994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python机器学习】实验02 线性回归

文章目录 线性回归1. 单变量的线性回归1.1 数据读取1.2 训练数据的准备1.3 假设函数定义--假设函数是为了去预测1.4 损失函数的定义1.5 利用梯度下降算法来优化参数w1.6 可视化误差曲线1.7 可视化回归线/回归平面 1.2 单变量的线性回归--基于sklearn试试&#xff1f;1.3 多变量…

object tracking论文代码汇总

文章目录 2023Segment and Track AnythingTrack Anything: Segment Anything Meets VideosSAM-DA: UAV Tracks Anything at Night with SAM-Powered Domain Adaptation 2023 Segment and Track Anything code&#xff1a;https://github.com/z-x-yang/Segment-and-Track-Anyt…

响应式赋值Object.assign()和JSON.parse(JSON.stringify())的区别

一、需求&#xff1a;点击编辑弹出编辑框&#xff0c;修改后的内容点击认按钮修改后的数据更新回显到原列表。今天优化代码的时候发现了Object.assign()和JSON.parse(JSON.stringify())的区别。 优化前代码如下&#xff1a; // 编辑药品回显editMedicData(data) {this.table…

会员系统怎么搭建,适合门店的会员系统有哪些?

会员系统是一种为企业和门店提供会员管理和服务的工具。会员系统可以通过提供专属优惠、积分奖励、个性化推荐等方式&#xff0c;激励顾客成为会员并保持长期关系。 我们在自己搭建或选择会员系统时&#xff0c;需要考虑门店的特定需求以及系统的功能、可靠性、易用性和成本等因…

github前端开源json2html

软件介绍 前端低代码工具包&#xff0c;通过 JSON 配置就能生成各种页面。 应用场景 json解析超大数据动态渲染&#xff0c;渲染速度、性能解决问题 包引用列表 vue3 (cdn模式开发)element plusnodehttp-serveraxios 操作步骤 1.环境准备下载node&#xff1a;https://no…

长tree用buffer还是inverter?驱动强度如何选型?

相关文章链接: 静态时序分析: 最小脉冲宽度检查 redhawk:clock buffer cluster 面试中关于CTS buf/inv选型的问题经久不衰,依托经验,不看纸面信息,inverter和buffer各有优劣,同驱动buffer实际推力更强,意味着只用buffer,clock repeater数量更少,inverter必须成对的…

从零开始搭建医药领域知识图谱实现智能问答与分析服务(含码源):含Neo4j基于垂直网站数据的医药知识图谱构建、医药知识图谱的自动问答等

项目设计集合&#xff08;人工智能方向&#xff09;&#xff1a;助力新人快速实战掌握技能、自主完成项目设计升级&#xff0c;提升自身的硬实力&#xff08;不仅限NLP、知识图谱、计算机视觉等领域&#xff09;&#xff1a;汇总有意义的项目设计集合&#xff0c;助力新人快速实…

抖音seo矩阵系统源码保姆式开发部署指导

抖音seo霸屏&#xff0c;是一种专为抖音视频创作者和传播者打造的视频批量剪辑&#xff0c;批量分发产品。使用抖音seo霸屏软件&#xff0c;可以帮助用户快速高效的制作出高质量的优质视频。 使用方法&#xff1a;1. 了解用户的行为习惯 2. 充分利用自身资源进行开发 3. 不…

超全整理,selenium自动化测试常见问题解决(汇总)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 自动化代码中&…

静态路由小实验

文章目录 一、实验要求及拓扑图二、实验步骤三、思考题 一、实验要求及拓扑图 二、实验步骤 1、创建VLAN&#xff0c;将端口划入vlan 在交换机S3、S4上创建VLAN10、20 Switch(config)#vl 10 Switch(config-vlan)#vl 20 S3(config)#int f0/3 S3(config-if)#switchport access …

SpringBoot中使用测试框架MockMvc来模拟HTTP请求测试Controller接口

场景 Java中进行单元测试junit.Assert断言、Mockito模拟对象、verify验证模拟结果、Java8中lambda的peek方法使用&#xff1a; Java中进行单元测试junit.Assert断言、Mockito模拟对象、verify验证模拟结果、Java8中lambda的peek方法使用_assert java8_霸道流氓气质的博客-CSD…

17的勒索软件攻击泄露关键OT信息

数据泄漏一直是企业关注的问题&#xff0c;敏感信息泄露可能导致声誉受损、法律处罚、知识产权损失、甚至影响员工和客户的隐私。然而很少有关于工业企业面临的威胁行为者披露其OT安全、生产、运营或技术的敏感细节的研究。 2021年&#xff0c;Mandiant威胁情报研究发现&#…

ambari管理配置组实现针对不同节点使用不同配置

实操 一.新建配置组&#xff1a; 二.取名后指定该配置组针对哪些节点生效&#xff1a; 三.添加节点&#xff1a; 保存后有个空的配置组newMR2. 四.接下来在该配置组内自定义一些配置参数&#xff0c;比如单独针对节点hdp01配置fetch最高并发度为20&#xff1a; 五.重…

区块链服务网络的顶层设计与应用实践

日前&#xff0c;2023全球数字经济大会专题论坛&#xff1a;Web3.0发展趋势专题论坛暨2023区块链、元宇宙蓝皮书发布会在北京举行。本次论坛上隆重发布了《中国区块链发展报告&#xff08;2023&#xff09;》&#xff0c;对我国区块链行业在2022年的发展状况进行了总结梳理&…

【英飞凌PSoC 6】使用软件和硬件I2C点亮OLED屏,帧率从2FPS提升到51FPS

文章目录 一、准备工作1.1 硬件准备1.2 软件准备1.3 硬件连接 二、原理分析2.1 开发板原理图2.2 芯片数据手册 三、软件I2C驱动OLED3.1 创建RT-Thread项目3.2 添加ssd1306软件包3.3 配置软件I2C和ssd1306软件包3.4 编译和下载程序3.5 运行和测试程序 四、硬件I2C驱动OLED4.1 增…

depcheck检测缺失哪些依赖包

npm install -g depcheck 如果不想全局安装,npm i depcheck后可以在package.json的scripts中输入 "check": "depcheck" 之后使用 npm run check depcheck - npm 超级好用的依赖检查工具depcheck【渡一教育】_哔哩哔哩_bilibili

Cron 选择器

// 定义一个名为 cron 的新组件 Vue.component(cron, {name: cron,props: [data],data() {return {second: {cronEvery: ,incrementStart: 3,incrementIncrement: 5,rangeStart: ,rangeEnd: ,specificSpecific: [],},minute: {cronEvery: ,incrementStart: 3,incrementIncremen…

[Tools: tiny-cuda-nn] Linux安装

official repo: https://github.com/NVlabs/tiny-cuda-nn 该包可以显著提高NeRF训练速度&#xff0c;是Instant-NGP、Threestudio和NeRFstudio等框架中&#xff0c;必须使用的。 1. 命令行安装 最便捷的安装方式&#xff0c;如果安装失败考虑本地编译。 pip install ninja g…

QT第一讲

思维导图 手动实现登录框 要求&#xff1a; 1、登录窗口更改标题、图标 2、设置固定尺寸、并给定一定的透明度 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>#include<QWidget> #include<QDebug> //信息调试类&#xff0c;用于打印输出…

谱瑞PS186芯片DP1.4转HDMI2.0主动式4K转换线DSC支持10bit色深HDR

PS186 是 DisplayPort ™ v1.4a 到 HDMI ™ 2.0b 视频接口转换器&#xff0c;非常适合电缆适配器、扩展坞、监视器、电视接收器和其他需要视频协议转换的应用。输入是一个 DP v1.4a 兼容的上行端口&#xff0c;它接受音频和显示数据&#xff0c;输出是一个 HDMI 2.0b 兼容的下行…