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

文章目录

  • 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)指数阶

在这里插入图片描述

本章完.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pytest 自定义HOOK函数

除了系统提过的HOOK函数外&#xff0c;也可以通过自定义HOOK的方式实现想要的功能。 首先创建一个py文件&#xff0c;里面定义自己的HOOK函数&#xff0c;主要pytest里面的hook函数必须以pytest开头。 #myhook.pydef pytest_myhook(user):"""自定义HOOK函数&q…

element时间选择器的默认值

概览&#xff1a;vue使用element组件&#xff0c;需要给时间选择器设置默认值&#xff0c;场景一&#xff1a;默认时间选择器&#xff0c;场景二&#xff1a;时间范围选择器&#xff0c;开始时间和结束时间。 一、默认时间选择器 实现思路&#xff1a; element组件的v-model绑…

【C语言学习——————动态内存管理】

文章目录 一、什么是动态内存管理二、动态内存函数的介绍 1.malloc函数的介绍2.calloc函数的介绍3.realloc函数的介绍三、free函数的介绍 一.什么是动态内存管理 我们知道数据都是在内存中进行储存的&#xff0c;但是如果我们需要调用内存&#xff0c;我们可以通过定义一个变量…

param.grad、requires_grad、grad_fn、grad/梯度为None?

基本概念 1&#xff09;is_leaf 叶子节点和非叶子节点的区别&#xff1a;计算图中的节点分为叶子节点和非叶子节点&#xff0c;叶子节点可以理解成没有其他tensor再利用它进行计算&#xff08;例如b a1&#xff0c;那么b需要a进行计算&#xff0c;那么a就不是叶子结点&…

day38-Mobile Tab Navigation(手机tab栏导航切换)

50 天学习 50 个项目 - HTMLCSS and JavaScript day38-Mobile Tab Navigation&#xff08;手机tab栏导航切换&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"…

Git克隆文件不显示绿色勾、红色感叹号等图标

1、问题 Git和TorToiseGit安装后&#xff0c;Git克隆的文件不会显示绿色勾、红色感叹号等图标。 2、检查注册表 2.1、打开注册表 (1)WinR打开运行窗口&#xff0c;输入regedit&#xff0c;点击确定&#xff0c;打开注册表编辑器。 2.2、找如下路径 (1)找到路径 计算机\HKEY_…

Java开发环境以及项目搭建案例汇总

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 友情提示 1、 假若你的设备已有可用的Java开发基础环境&#xff0c;则无需重新搭建 2、 假若你需重新搭建Java开发&#xff0c;请务必彻底卸载之前的环境 3、 请尽量保证与…

RocketMQ 行业分享

5.0的架构发生了重大调整&#xff0c;添加了一层rocketmq-proxy,可以通过grpc的方式接入。 参考 https://juejin.cn/post/7199413150973984827

win10安装vs6行号插件

插件包名&#xff1a;win10 VC6LineNumberAddin 下载包&#xff1a; 链接: https://pan.baidu.com/s/13T-NAxQQDcA_K1hHJQ0vWw?pwdbe3r 提取码: be3r 修改reg为以下&#xff1a; Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\DavidHowe Software\Lie…

JAVA SE -- 第十一天

&#xff08;全部来自“韩顺平教育”&#xff09; 异常-Exception 一、异常介绍 1、基本介绍 Java语言中&#xff0c;将程序执行中发生的不正常情况为“异常”&#xff08;开发过程中的语法错误和逻辑错误不是异常&#xff09; 2、执行过程中发生的异常事件可分为两大类 …

MySQL基础(五)主从复制及读写分离

目录 前言 一、概述 &#xff08;一&#xff09;、MySQL Replication &#xff08;二&#xff09;、MySQL复制类型 &#xff08;三&#xff09;、MySQL支持的复制方式 二、部署MySQL主从异步复制 &#xff08;一&#xff09;、master&#xff08;主&#xff09; &#x…

带你读论文第三期:微软研究员、北大博士陈琪,荣获NeurIPS杰出论文奖

Datawhale干货 来源&#xff1a;WhalePaper&#xff0c;负责人&#xff1a;芙蕖 WhalePaper简介 由Datawhale团队成员发起&#xff0c;对目前学术论文中比较成熟的 Topic 和开源方案进行分享&#xff0c;通过一起阅读、分享论文学习的方式帮助大家更好地“高效全面自律”学习&…

Docker Compose 容器编排 + Docker--harbor私有仓库部署与管理

目录 一、Docker Compose简介 1、Docker Compose 的YAML 文件格式及编写注意事项 2、Docker compose 使用的三个步骤 3、 Docker Compose配置常用字段 4、 Docker Compose 常用命令 5、 Docker Compose 文件结构 二&#xff1a; Docker Compose 安装 1、Docker Compose…

基于多场景的考虑虑热网网损的太阳能消纳能力评估研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

勒索病毒最新变种.locked勒索病毒来袭,如何恢复受感染的数据?

引言&#xff1a; 在数字时代&#xff0c;黑客们的阴谋不断蔓延&#xff0c;其中.locked勒索病毒是备受关注的黑暗力量。它们犹如黑夜中的黑暗之星&#xff0c;迅速将用户的数据加密&#xff0c;要挟赎金。本文91数据恢复将深入揭示.locked勒索病毒的独特之处&#xff0c;并探…

【Lua学习笔记】Lua入门

文章目录 Lua变量数据类型变量声明其他表示 Lua语法判断逻辑判断&#xff08;Lua很特殊&#xff0c;这个比较重要&#xff09;短路判断 ifif else 循环whileforrepeat 迭代器泛型for迭代器无状态迭代器多状态的迭代器 Lua函数select方法 数组字符索引_G &#xff08;不是教程&a…

13、PHP面向对象2(方法的访问控制、子类继承、常量)

1、类中的方法可以被定义为公有&#xff0c;私有或受保护。如果没有设置这些关键字&#xff0c;则该方法默认为公有。 public定义的方法&#xff0c;可以在类外使用。 protected定义的方法&#xff0c;只能在本类或子类的定义内使用。 private定义的方法&#xff0c;只能在本…

【Java中间件】RocketMQ

RocketMQ 一、MQ概述 Message Queue&#xff0c;是一种提供消息队列服务的中间件。提供了消息生产、存储、消费全过程API的软件系统。 MQ的作用 限流削峰&#xff1a;当用户发送超量请求时&#xff0c;将请求暂存&#xff0c;以便后期慢慢处理。如果不使用MQ暂存直接请求到…

win10系统wps无法启动(打开文档)

我的win10系统中&#xff0c;之前可以顺畅地打开wps&#xff0c;但最近无法打开文档&#xff0c;停留在启动页面&#xff0c;在任务管理器中可以看到启动的wps线程&#xff0c;如果继续双击文档&#xff0c;线程增加&#xff0c;但依然无法打开文档。 wps版本是刚刚更新的15120…

用于提取数据的三个开源NLP工具

开发人员和数据科学家使用生成式AI和大语言模型&#xff08;LLM&#xff09;来查询大量文档和非结构化数据。开源LLM包括Dolly 2.0、EleutherAI Pythia、Meta AI LLaMa和StabilityLM等&#xff0c;它们都是尝试人工智能的起点&#xff0c;可以接受自然语言提示&#xff0c;生成…