【DS】算法的时间复杂度与空间复杂度

简介: 算法的时间复杂度与空间复杂度

@[toc]

算法的时间复杂度与空间复杂度

反爬链接

1. 算法效率

引:如何衡量一个算法的好坏呢?

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

在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度(摩尔定律:硬件每18个月翻倍)。所以我们如今已经不需要再特别关注一个算法的空间复杂度,重点关注时间复杂度

2. 时间复杂度

2.1 时间复杂度的概念

在计算机科学中,算法的时间复杂度是一个 函数(是含未知数的数学表达式啦hh),它定量描述了该算法的运行时间。

计算时间复杂度不是计算时间。一个算法执行所耗费的时间,理论上是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们如果每个算法都上机测试,就很麻烦;且运行环境不同,花费时间也不同。

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

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

2.2 大O渐进表示法

:key: 大O渐进表示法
  1. 常数1表示运行次数中所有加法常数
  2. 在修改后的运行次数的函数中,保留最高阶项
  3. 如果最高阶存在且不为1,则去除此项的系数

例 ——

// 请计算一下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的基本执行次数:

$$ F(N) = N^2 + 2N + 10 $$

N F(N) = N^2 + 2*N + 10 N^2
N = 10 130 100
N = 100 1,0210 1,0000
N = 1000 100,2010 100,0000

由上表可以看出,N越大,后两项对于结果的影响越小。因此,保留对结果影响最大的项,即可代表大概的执行次数。

计算时间复杂度时,无需计算精确次数,只需要计算大概的执行次数,那么这里我们使用大O的渐进表示法Big O notation:用来描述函数渐进行为的数学符号)。—— 估算

:yellow_heart:因此,用大O渐进表示法后,Func1的时间复杂度为:

$$ O(N^2) $$

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

基本执行次数 ——

$$ F(N) = 2*N+10 $$

保留最高项,并去掉最高项系数后得

:yellow_heart:时间复杂度 ——

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

若题目有条件,
在这里插入图片描述
:yellow_heart:这里没有说明,因此时间复杂度 ——

$$ O(M+N) $$

实例3 ——

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

基本执行次数 ——

$$ F(N) = 100 $$

常数项用1代替后得

:yellow_heart:时间复杂度 ——

$$ O(1) $$

当题目中要求,将时间复杂度优化至O(1),不是代表运算一次,而是常数次。

实例4 —— 时间复杂度是做悲观预期

// 计算strchr的时间复杂度?--由strstr联想,这是在字符串中查找字符的库函数
const char* strchr ( const char* str, int character );
const char* strchr ( const char* str, int character )
{
    while(*str != '\0')
    {
        if(*str == character)
        {
            return str;
        }
        str++;
    }
    return NULL;
}

当随着代码的输入不同,时间复杂度不同 ——

(例如:在一个长度为N数组中搜索一个数据x) hello world\0

  • 最坏情况(查找d):任意输入规模的最大运行次数(上界) —— [F(N) = N次找到]
  • 平均情况(查找w):任意输入规模的期望运行次数 —— [F(N) = N/2次找到]
  • 最好情况(查找h):任意输入规模的最小运行次数(下界) —— [F(N) = 1次找到]

要明白时间复杂度是做悲观预期,看最坏情况。

:yellow_heart:因此,这段代码的时间复杂度为 ——

$$ O(N) $$

:key:计算时间复杂度,要会用 思想计算

接下来再来看两段代码 ——

实例5 —— 冒泡排序·思想计算

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n) 
{
     assert(a);
     for (size_t end = n; end > 0; --end)
     {
        int flag = 0;
        for (size_t i = 1; i < end; ++i)
        {
            if (a[i-1] > a[i])
            {
                Swap(&a[i-1], &a[i]);
                flag = 1;
            }
         }
         if (flag == 0)
         break;
        }
}

这里不能因为嵌套了两层循环,就简单的认为时间复杂度为O(N)

思想分析 —— $N$个数字,共排$N-1$趟,每一趟进行$N-i-1$次比较(第$i$趟,$i= 0,1,2...$),这过程太熟悉了,就不赘述了

最坏的情况(由等差数列公式):

$$ F(N) = (N-1)+(N-2)+...+2+1 = (N-1+1)(N-1)/2 $$

最好的情况(已经有序):

$$ F(N) = N-1 $$

因此冒泡排序的时间复杂度为

$$ O(N^2) $$

实例6 —— 二分查找·思想计算

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

在这里插入图片描述

因此,二分查找的时间复杂度为

$$ O(logN) $$

如果我要在14亿人口中查找一个人,用二分查找要查找几次?

N个数中查找 能查找的人数 查找次数
2^10 1024 10
2^20 100w 20
2^30 10亿 30(变化很不明显)

只需要查找31次。

这里我们要认识到二分查找还算是很nb的算法,当然了,排成有序也是有消耗的。

实例7、8-—— 递归·时间复杂度计算

:key: 递归算法计算时间复杂度:

$$ O(N) = 递归次数*每次递归调用中的次数 $$

每次递归调用中的次数是指 —— 一次递归调用,基本操作的次数。

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

    return Fac(N - 1)*N;
}

在这里插入图片描述

下一个 ——

//计算斐波那契额数列Fib递归的时间复杂度
long long Fib(size_t N)
{
    if(N < 3)
        return 1;
    
    return Fib(N-1) + Fib(N-2);
}

在这里插入图片描述

因此,时间复杂度为 ——

$$ O(2^N) $$

实际上,斐波那契额数列的递归写法我们基本不用,因为太慢了,采用迭代就okk

3. 空间复杂度

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

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

空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法

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

实例1 ——

// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n) 
{
    assert(a);
    for (size_t end = n; end > 0; --end)
    {
        int flag = 0;
        for (size_t i = 1; i < end; ++i)
        {
            if (a[i - 1] > a[i])
            {
                Swap(&a[i - 1], &a[i]);
                flag = 1;
            }
        }
        if (flag == 0)
            break;
    }
}

在这里插入图片描述

:yellow_heart:因此空间复杂度为 ——

$$ 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;

:yellow_heart:空间复杂度为 ——

$$ O(N) $$

时间复杂度为 ——

$$ O(N) $$

当然,如果只是要求斐波那契数列中的第n个数,那就不需要额外申请n个空间,空间复杂度即为 ——

$$ O(1) $$

实例3 ——

如何计算阶乘递归的空间复杂度?—— 递归深度

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

在这里插入图片描述

:yellow_heart:因此空间复杂度为 ——

$$ O(N) $$

4. 常见复杂度的对比

:key:时间复杂度不计算时间,计算大概运算次数

:key:空间复杂度不计算空间,计算大概定义变量的个数

这里要对各种时间复杂度的好坏有认知。

img

相关文章
|
2月前
|
机器学习/深度学习 算法 存储
[数据结构]——算法的时间复杂度和空间复杂度
[数据结构]——算法的时间复杂度和空间复杂度
|
16天前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-2
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
16天前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-1
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
25天前
|
存储 算法 C语言
数据结构和算法学习记录——初识 数据结构和算法&时间复杂度
数据结构和算法学习记录——初识 数据结构和算法&时间复杂度
15 4
|
25天前
|
算法
数据结构和算法学习记录——时间复杂度、空间复杂度相关练习题
数据结构和算法学习记录——时间复杂度、空间复杂度相关练习题
13 2
|
25天前
|
算法
数据结构和算法学习记录——时间复杂度的计算(嵌套循环、大O的渐进表示法、双重循环、常数循环、strchr、冒泡排序、二分查找、斐波那契数列递归)
数据结构和算法学习记录——时间复杂度的计算(嵌套循环、大O的渐进表示法、双重循环、常数循环、strchr、冒泡排序、二分查找、斐波那契数列递归)
32 1
|
2月前
|
算法
说说你对算法中时间复杂度,空间复杂度的理解?如何计算?
该文介绍了算法的基本概念,强调了时间和空间复杂度在衡量算法效率中的重要性。时间复杂度表示算法执行时间与输入规模的增长关系,常用大O符号表示,如O(1), O(log n), O(n), O(nlogn), O(n^2)等。文章指出,最坏情况下的时间复杂度是评估算法性能的上限,并且在实际应用中需要在时间与空间之间找到平衡。
|
24天前
|
存储 算法 C语言
数据结构和算法——堆排序(选择排序、思路图解、代码、时间复杂度、堆排序及代码)
数据结构和算法——堆排序(选择排序、思路图解、代码、时间复杂度、堆排序及代码)
16 0
|
24天前
|
算法 Shell C语言
数据结构与算法——希尔排序(引例、希尔增量序列、原始希尔排序、代码、时间复杂度、Hibbard增量序列、Sedgewick增量序列)
数据结构与算法——希尔排序(引例、希尔增量序列、原始希尔排序、代码、时间复杂度、Hibbard增量序列、Sedgewick增量序列)
15 0
|
24天前
|
人工智能 算法 C语言
数据结构与算法——简单排序-冒泡排序、插入排序,时间复杂度下界(图示、代码、时间复杂度、定理)
数据结构与算法——简单排序-冒泡排序、插入排序,时间复杂度下界(图示、代码、时间复杂度、定理)
18 0