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

Posted 英雄哪里出来

tags:

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

一、前言

  很多人觉得算法难,是因为被困在了时间和空间这两个维度上。如果不考虑时间和空间的因素,其实我们可以把所有问题都通过穷举解决,也就是你告诉计算机你要做什么,然后通过它强大的算力帮你计算。
  今天,我就和大家来聊一下时间复杂度。在这里插入图片描述

二、穷举

1、小试牛刀

  • 所谓穷举,也就是我们通常所说的枚举,就是把所有情况都遍历了的意思。举个最简单的例子:

【例1】给定 n ( n ≤ 1000 ) n(n \\le 1000) n(n1000) 个元素 a i a_i ai,求其中是偶数的数的个数。

  • 判断一个数是偶数还是奇数,只需要求它除上 2 的余数是 0 还是 1,那么我们把所有数都判断一遍,并且对符合条件的情况进行计数,最后返回这个计数器就是答案,这里需要遍历所有的数,这就是穷举。如图二-1-1所示:
    图二-1-1
  • c++ 代码实现如下:
int countEven(int n, int a[]) {
    int cnt = 0;
    for(int i = 0; i < n; ++i) {
        if(a[i] % 2 == 0)
            ++cnt;
    }
    return cnt;
}

在这里插入图片描述

略带不屑的你

2、乘胜追击

  • 经过上面的例子,相信读者对穷举已经有一定的理解,那么我们来看看稍微复杂一点的情况。

【例2】给定 n ( n ≤ 1000 ) n(n \\le 1000) n(n1000) 个元素 a i a_i ai,求有多少个二元组 ( i , j ) (i,j) (i,j),满足 a i + a j a_i + a_j ai+aj 是偶数 ( i < j ) (i \\lt j) (i<j)

  • 秉承穷举的思想,并且这里需要两个变量 i i i j j j,所以可以枚举 a i a_i ai a j a_j aj,再对 a i + a j a_i + a_j ai+aj 进行奇偶性判断,所以很快设计出一个利用穷举的算法。如图二-2-1所示:
    图二-2-1
  • c++ 代码实现如下:
int countEvenPair(int n, int a[]) {
    int cnt = 0;
    for(i = 0; i < n; ++i) {
        for(j = i+1; j < n; ++j) {
            if( (a[i] + a[j]) % 2 == 0)
                ++cnt;
        }
    }
    return cnt;
}

若有所思的你

3、举一反三

  • 经过这两个例子,是不是对穷举已经有点感觉了?那么,我们继续来看下一个例子。

【例3】给定 n ( n ≤ 1000 ) n(n \\le 1000) n(n1000) 个元素 a i a_i ai,求有多少个三元组 ( i , j , k ) (i,j,k) (i,j,k),满足 a i + a j + a k a_i + a_j + a_k ai+aj+ak 是偶数 ( i < j < k ) (i \\lt j \\lt k) (i<j<k)

  • 给 10 秒倒计时,思考一下!
    在这里插入图片描述
    图二-3-1
  • 相信聪明的你也已经猜到了,直接给出代码:
int countEvenTriple(int n, int a[]) {
    int cnt = 0;
    for(i = 0; i < n; ++i) {
        for(j = i+1; j < n; ++j) {
            for(int k = j+1; k < n; ++k) {
                if( (a[i] + a[j] + a[k]) % 2 == 0)
                    ++cnt;
            }
        }
    }
    return cnt;
}
  • 要求多少个元组,就是枚举多少层循环。
    完全懂了的你

4、返璞归真

【例4】给定 n ( n ≤ 1000 ) n(n \\le 1000) n(n1000) 个元素 a i a_i ai 和一个整数 k ( k ≤ n ) k (k \\le n) k(kn),求有多少个有序 k k k 元组,满足 它们的和 是偶数。

  • 一层循环,两层循环,三层循环, k k k 层循环?
  • 我们需要根据 k k k 的不同,决定写几层循环, k k k 的最大值为 1000,也就意味着我们要写 1000 的 i f   e l s e if \\ else if else
  • 显然,这样是无法接受,比较暴力的做法是采用到递归,c++ 代码实现如下:
int dfs(int n, int a[], int start, int k, int sum) {
    if(k == 0)
        return sum % 2 ? 0 : 1;               // (1)
    int s = 0; 
    for(int i = start; i < n; ++i)
        s += dfs(n, a, i+1, k-1, sum + a[i]); // (2)
    return s;
}

"啊这…" 的你

  • 1)dfs(int n, int a[], int start, int k, int sum)这个函数的含义是:给定 n n n 元素的数组 a [ ] a[] a[],从下标 s t a r t start start 开始,选择 k k k 个元素,得到的和为 s u m sum sum 的情况下的方案数,当 k = 0 k=0 k=0 时代表的是递归的出口;
  • 2)当前第 i i i 元素选择以后,剩下就是从 i + 1 i+1 i+1 个元素开始选择 k − 1 k-1 k1 个的情况,递归求解。
  • 我们简单分析一下, n n n 个元素选择 k k k 个,根据排列组合,方案数为: C n k C_n^k Cnk,当 n = 1000 , k = 500 n=1000,k=500 n=1000k=500 时已经是天文数字,这段代码是完全出不了解的。

当然,对于初学者来说,这段代码如果不理解,问题也不大,只是为了说明穷举这个思想。

充满求知欲的你

三、时间复杂度

1、时间复杂度的表示法

1、时间函数

  • 时间复杂度往往会联系到一个函数,自变量表示规模,应变量表示执行时间。
  • 这里所说的执行时间,是指广义的时间,也就是单位并不是 “秒”、“毫秒” 这些时间单位,它代表的是一个 “执行次数” 的概念。我们用 f ( n ) f(n) f(n) 来表示这个时间函数。

2、经典函数举例

  • 在【例1】中,我们接触到了单层循环,这里的 n n n 是一个变量,随着 n n n 的增大,执行次数增大,执行时间就会增加,所以就有了时间函数的表示法如下:
  • f ( n ) = n f(n) = n f(n)=n
    在这里插入图片描述
    图三-2-1
  • 这个就是最经典的线性时间函数。
  • 在【例2】中,我们接触到了双层循环,它的时间函数表示法如下:
  • f ( n ) = n ( n − 1 ) 2 f(n) = \\frac {n(n-1)} 2 f(n)=2n(n1)
    在这里插入图片描述
    图三-2-2
  • 这是一个平方级别的时间函数。
  • 在【例3】中,我们接触到了三层循环,它的时间函数表示法如下:
  • f ( n ) = n ( n − 1 ) ( n − 2 ) 6 f(n) = \\frac {n(n-1)(n-2)} 6 f(n)=6n(n1)(n2)
    在这里插入图片描述
    图三-2-3
  • 这是一个立方级别的时间函数。

3、时间复杂度