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

一、时间复杂度

1.常量阶O(1)

int i = 8;
int j = 6;
int sum = i + j;

       只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度我们都记作 O(1)。或者说,一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。

 

2.线性阶、平方阶、立方阶....

int cal(int n) {
    int sum = 0;
    int i = 1;
    for (; i <= n; ++i) {
        sum = sum + i;
    }
    return sum;
}

for循环内的代码执行了n次,所以总的时间复杂度就是 O(n)。

两层for循环就是O(n^2).....等。

 

3.对数阶O(logn)与线性对数阶O(nlogn)

对数阶:

i=1;
while (i <= n) {
    i = i * 2;
}

通过 2^x =n 求解 x ,x=log2n,所以,这段代码的时间复杂度就是 O(log n)。

注意:不管是以 2 为底、以 3 为底,还是以 10 为底,我们可以把所有对数阶的时间复杂度都记为 O(logn)

线性对数阶:

O(nlogn) :一段代码的时间复杂度是 O(logn),我们循环执行 n 遍,时间复杂度就是O(nlogn) ,常见的有归并排序、快速排序。

 

4. O(m+n)、O(m*n)

O(m+n):
代码的复杂度由两个数据的规模来决定,先看代码!

int cal(int m, int n) {
    int sum_1 = 0;
    int i = 1;
    for (; i < m; ++i) {
        sum_1 = sum_1 + i;
    }
    int sum_2 = 0;
    int j = 1;
    for (; j < n; ++j) {
        sum_2 = sum_2 + j;
    }
    return sum_1 + sum_2;
}

m 和 n 是表示两个数据规模。我们无法事先评估 m 和 n 谁的量级大,所以我们在表示复杂度的时候,就不能简单地利用加法法则,省略掉其中一个。所以,上面代码的时间复杂度就是 O(m+n)。


O(m*n):与指数阶的类似。

for (; i <= n; ++i) {
    j = 1;
    for (; j <= m; ++j) {
        sum_3 = sum_3 + i * j;
    }
}

二、浅析最好、最坏、平均、均摊时间复杂度

1.最好、最坏情况时间复杂度

          下面代码是在一个无序的数组(array)中,查找变量x 出现的位置。如果没有找到,就返回 -1,时间复杂度是O(n)。

// n 表示数组 array 的长度
int find(int[] array, int n, int x) {
    int i = 0;
    int pos = -1;
    for (; i < n; ++i) {
        if (array[i] == x) pos = i;
    }
    return pos;
}

      我们在数组中查找一个数据,并不需要每次都把整个数组都遍历一遍,因为有可能中途找到就可以提前结束循环了。但是,这段代码写得不够高效。我们可以这样优化一下这段查找代码。

// n 表示数组 array 的长度
int find(int[] array, int n, int x) {
    int i = 0;
    int pos = -1;
    for (; i < n; ++i) {
        if (array[i] == x) {
            pos = i;
            break;
        }
    }
return pos;
}

我们优化完之后,这段代码的时间复杂度还是 O(n) 吗?

最好情况时间复杂度:就是,在最理想的情况下,执行这段代码的时间复杂度。要查找的变量 x 正好是数组的第一个元素,这个
时候对应的时间复杂度就是最好情况时间复杂度。——O(1)
最坏情况时间复杂度:就是,在最糟糕的情况下,执行这段代码的时间复杂度。就像刚举的那个例子,如果数组中没有要查找的变量 x,我们需要把整个数组都遍历一遍才行,所以这种最糟糕情况下对应的时间复杂度就是最坏情况时间复杂度——O(n)
 

2.平均情况时间复杂度

借助刚才查找变量 x 的例子来给你解释。

       要查找的变量 x,要么在数组里,要么就不在数组里。我们假设在数组中与不在数组中的概率都为 1/2。另外,要查找的数据出现在 0~n-1 这 n 个位置的概率也是一样的,为 1/n。所以,根据概率乘法法则,要查找的数据出现在 0~n-1 中任意位置的概率就是 1/(2n)。

这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度。引入概率之后,前面那段代码的加权平均值为 (3n+1)/4。用大 O 表示法来表示,去掉系数和常量,这段代码的加权平均时间复杂度仍然是 O(n)。

 

3.均摊时间复杂度

// array 表示一个长度为 n 的数组
// 代码中的 array.length 就等于 n
int[] array = new int[n];
int count = 0;
void insert(int val) {
    if (count == array.length) {
        int sum = 0;
        for (int i = 0; i < array.length; ++i) {
            sum = sum + array[i];
        }
        array[0] = sum;
        count = 1;
    }
    array[count] = val;
    ++count;
}

      最理想的情况下,数组中有空闲空间,我们只需要将数据插入到数组下标为 count 的位置就可以了,所以最好情况时间复杂度为 O(1)。最坏的情况下,数组中没有空闲空间了,我们需要先做一次数组的遍历求和,然后再将数据插入,所以最坏情况时间复杂度为 O(n)。
      假设数组的长度是 n,根据数据插入的位置的不同,我们可以分为 n 种情况,每种情况的时间复杂度是 O(1)。除此之外,还有一种“额外”的情况,就是在数组没有空闲空间时插入一个数据,这个时候的时间复杂度是 O(n)。而且,这 n+1 种情况发生的概率一样,都是1/(n+1)。所以,根据加权平均的计算方法,我们求得的平均时间复杂度就是:

平均复杂度分析其实并不需要这么复杂!
       继续看在数组中插入数据的这个例子。每一次 O(n) 的插入操作,都会跟着 n-1 次O(1) 的插入操作,所以把耗时多的那次操作均摊到接下来的 n-1 次耗时少的操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是 O(1)。这就是均摊分析的大致思路。
 

三、空间复杂度分析

一般只需要看占用的最大的空间即可,即下面这段代码申请了一个大小为 n 的 int 类型数组,除此之外,剩下的代码都没有占用更多的空间,所以整段代码的空间复杂度就是 O(n)。

void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i <n; ++i) {
    a[i] = i * i;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值