你真搞懂时间复杂度和空间复杂度了吗

Posted 玄鸟轩墨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你真搞懂时间复杂度和空间复杂度了吗相关的知识,希望对你有一定的参考价值。

@toc

写在前面

说实话,我已经学了不少的数据结构了,第一次接触时间和空间复杂度的时候感觉有些不好理解,这篇博客就搁置下来了。这些天感觉自己理清楚了。所以开始和大家分享。要是有什么错误的地方,还请大家指正.

看完我们就会发现,计算复杂度,我们就是计算一个大概,我们平常遇到的问题复杂度不是O(1)就是O(N),其他的一般很少见.

算法效率

在谈复杂度之前,我们必须说一下什么是好的代码,好的代码本质就是耗费算力最少的代码,我们都知道斐波那契数列,下面让我们看看用递归方法来实现会怎么样

#include <stdio.h>

int val = 0;

int Fib(int n)

    val++;
    if (n < 3)
    
        return 1;
    
    return Fib(n - 1) + Fib(n - 2);


int main()

    int n = 10;
    Fib(n);
    printf("递归执行的次数 : %d\\n", val);
    return 0;


时间复杂度

在看到这个时间一词后,我首先想到的是便是一个程序执行所用的时间。我想这就是时间复杂度的解释吧。可是现实给了我一巴掌。要是我们用时间来衡量一个程序的好坏。那么厉害的计算机一定吊打老式计算机,一个程序出现两种不一样的复杂度,这不是荒谬吗! 后来我查了一下定义。原来是程序的一些代码的==执行次数==,我们先来看下定义

大O渐进法

我们计算时间复杂度和后面的空间复杂度都是使用大O渐进法,现在你可能还不太了解这是什么,后面你一看例子就可以明白了

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

我们可以轻易算出++count语句语句执行的次数是 ==N^2^ + 2 * N + 10==, 这对于我们来说是在是太简单了,so easy!!!那么这个函数的时间复杂度是 O(N^2^) ??? 我写了什么?为何是这样的?下面我把大O渐进法的规则说一下

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

    看了规则后,我们还是一头雾水,这究竟什么跟什么,我是看不懂。大家不要担心我这里每一条都给出解释

用常数1取代运行时间中的所有加法常数。

我们可以看到 N^2^ + ==2== * N + ==10== 里面 的 2 和 10 都是常数,都给我变成1得到 N^2^ + N + ==1==

只保留最高阶项

从第一步得到的结果是N^2^ + N + 1,我们只保留最高项,我们发现 ==N^2^==是最高项,就要它自己

如果最高阶项存在且不是1,则去除与这个项目相乘的常数

判断最高项的系数是不是 1 不是就给他变成1,假如最高项是 2N^2^ ,我们就把他变成 N^2^,这就是得到O(N^2^)

计算下面代码的时间复杂度

我们已经初步了解了时间复杂度的算法,下面我们就用几个例子来巩固一下。不过这些例子都是我们经常看到的类型,大家最好把结果和思想记住

多个变量

我们计算实际执行次数是 ==N + M==,由于不知道N和M的关系,随意时间复杂度就是 O(N+M) 。假如我们要是知道了M和N关系,比如说M<<N 那么时间复杂度就是O(N)

常数

这个就更简单了,实际次数就是 100 那么时间复杂度就是 O(1) 我们可能会有些疑惑,我们执行了1亿次是不是也是O(1),实际上是的,只要是常数次数 就是O(1)

二分查找

这个才是重点,我们对二分查找很熟悉,不过我们如何计算他的复杂度呢,对于这种我们可能有以下三种情况

  1. 最好情况 只查找一次就找到了
  2. 平均情况 查找多次才找到
  3. 最坏情况 最后还没有找到

那么我们应该用那种情况来计算时间复杂度呢?我们可以想一下,如果我们把一个项目送给上司,对他说这个代码的时间复杂度是O(1),但是当上司测试代码时候,发现很长时间才跑出了结果,你说他会怎么做:smile:我们应该考虑最坏情况,这也是大O渐进法所规定的。

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

递归

对于递归的函数时间复杂度我们也计算函数递归的次数,计算递归函数的时间复杂度是一大难点,我们要是了解函数的栈帧可能会容易一些。不过不用太担心这些笔试一般不会出现太难的问题

单路递归
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)

    if(0 == N)
        return 1;
    return Fac(N-1) * N;

我们假设 N的初始值是 10 后面会依次递归,直到 N = 0时,函数一共递归了N所以时间复杂度是O(N)。这里我无法用文字形象的表示出来,要是大家不理解可以在网上找些视频或者私信我。

多路递归
// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)

    if(N < 3)
        return 1;
    return Fib(N-1) + Fib(N-2);

这个斐波那契数列的递归更加困难了。要是理解二叉树可能会更好理解一点。这里我们还使 N = 10这样我们就理解了一点,没往下一层我们递归的次数 *2(一些细枝末节就不考虑了) .可以很轻易的知道我们有N层(实际小于N),那么时间复杂的就是 ==O(2^N^)==, 这就是我们最恶心的复杂度,也是很坏的代码.但我们N = 20时,会递归 1,048,576次.后面越来越恐怖


空间复杂度

空间复杂度的计算和时间复杂度的计算是一样了,都是使用大O渐进法,不过空间复杂度关注的使空间的大小.他比时间复杂度简单多了

我们还是来用例子演示

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== . 注意我们可能会对exchange 感觉到疑惑,他不是开辟了N次吗,是的,但是每一开辟的都是同一块空间,并且出了作用域就被销毁了.下面的例子可能会解决你的疑惑

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

这个空间复杂度是O(N),我们确实开辟了 N 个 long long类型的空间

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)

    if(N == 0)
        return 1;
    return Fac(N-1)*N;

这个我们虽然没有开辟变量的空间,但是我们开辟了N个函数栈帧,每个栈帧里面都是常数个所以空间复杂度是O(N)

以上是关于你真搞懂时间复杂度和空间复杂度了吗的主要内容,如果未能解决你的问题,请参考以下文章

算法入门什么是时间复杂度和空间复杂度,最优解

算法入门什么是时间复杂度和空间复杂度,最优解

时间复杂度和空间复杂度

❤️一文搞懂算法时间复杂度❤️

时间复杂度和空间复杂度计算方式

时间复杂度空间复杂度