时间复杂度与空间复杂度

目录

时间复杂度

空间复杂度

练手题

小结 


作为一个即将独当一面的程序员,不知道算法的时间复杂度与空间复杂度怎么行?作为一个计算机小白,每次看到屏幕上冰冷的“超出时间限制”时,都感觉备受打击。今天就来总结一下关于他们的知识点吧。

时间复杂度

定义:在计算机科学中,算法的时间复杂度是一个函数,定量描述了算法的运行时间。算法中基本操作的执行次数,即算法的时间复杂度。

基本操作:指算法中最频繁执行且耗时相对固定的操作)

大O表示法(Big O notation):

1、用1取代所有加法常数

2、只保留最高阶项

3、去除该项的乘法常数

这个过程得到的结果称为“大O阶”,目的在于去掉影响较小的项,通常用字母N来表示输入规模。

以下为常见的大O阶,按效率从高到低排序。

时间复杂度

名称

特点

O(1)

常数时间

运行时间(T)不随输入规模(N)变化。

O(log  n)

对数时间

(T)随(N)对数增长。如二分查找。

O(n)

线性时间

(T)随(N)正比增长。

O(n*log  n)

线性对数时间

(T)随(N)的变化呈现n与log n的乘积。

O(n*n)

平方时间

(T)与(N)的平方成正比。如冒泡排序。

O(n*n*n)

立方时间

(T)与(N)的立方成正比。

指数时间

(T)随(N)指数增长。

O(n!)

阶乘时间

(T)随(N)阶乘增长。

一些算法的时间复杂度存在最好情况、平均情况和最坏情况。如遍历数组寻找某个元素的算法。理想状况下遍历的第一个元素就满足了目标,从而终止算法。但在实际中一般关注算法的最坏运行情况,时间复杂度就用最坏情况表示

当算法的输入规模有两个或两个以上参数时,如果他们的关系相互独立,那么时间复杂度就是他们的和或积。如果参数之间存在关系,则根据关系简化,比如N远大于M时,可以将M简化掉。

递归算法的大O阶:O( 每次递归调用数量 ^ 递归次数 ) 如斐波那契数列,每次递归调用两次,复杂度为O(2^n)。

(如果每次递归只调用一次,则为O(N)。)

这里常常会用到等比数列与等差数列的计算:

空间复杂度

定义:算法的空间复杂度用于度量在运行过程中额外占用的储存空间大小。因为输入数据本身占用的空间是固定的,与算法的优劣无关,所以只讨论额外占用空间。

(可以看出时间复杂度讨论对CPU处理速度的影响,空间复杂度讨论对内存栈区的占用。)

也采用大O表示法。算法运行时的额外空间通常来自以下四个方面:

1、临时变量,例如以下代码段:

for (int i = 0; i < n; i++) {
	int sum = 0;
	cout << "Good" << endl;
}

对于变量“i”和“sum”,在循环中创建再销毁,每次占用的都是同一个空间。所以这段代码的空间复杂度为O(1)。所以不同于时间复杂度,空间可以重复利用的,一些空间可能在某些时机下销毁后再次创建。

2、数据结构,比如定义了一个大小为N的数组,大O阶就是O(N)了。

3、动态分配的内存,比如new或者malloc分配的。

4、递归调用栈

我们需要知道,当一个函数被调用时,系统会创建一个栈帧,保存该函数的返回地址、参数、局部变量等信息,然后压入栈中。在函数执行完毕后,栈帧会从栈中弹出,恢复到调用函数时的状态。

栈帧(Stack Frame)是函数调用栈中的一个重要概念,它是函数调用过程中用于保存函数上下文信息的一块内存区域。每次函数调用时,系统都会为其创建一个栈帧,并将其压入栈中;函数返回时,系统会从栈中弹出该栈帧。)

例如斐波那契数列:

int fibonacci(int n) {
    if (n <= 1) 
        return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

先开始调用fibonacci(n - 1),栈中压入一个新的栈帧,栈深度增加1。依此类推,栈的深度到达n-1时,递归开始返回,此时共创建了n-1个栈帧,在不断返回的过程中逐一销毁。一直返回到起点,紧接着现在开始调用fibonacci(n - 2),同理这边会创建n-2个栈帧,但是这里所用的空间仍然是上次调用时用过的空间,属于销毁后再利用,所以从整个算法的角度来看,最深的递归深度即为算法的空间复杂度,即O(N)。可以看出,递归算法的空间复杂度取决于递归深度,而不是递归调用的总数。

练手题

来瞅瞅下面这段看起来很愚蠢的代码,算算它的时间复杂度和空间复杂度是多少。(没错这就是我写的超出时间限制的算法)

        int maximumProduct(vector<int>& nums) {
            int mul;
            int max;
            bool first = true;
            int num1, num2, num3;
            for(int i = 0; i < nums.size(); i++){
                num1 = nums[i];
                for(int j = i + 1; j < nums.size(); j++){
                    num2 = nums[j];
                    for(int k = j + 1; k < nums.size(); k++){
                        num3 = nums[k];
                        if(first){
                            first = false;
                            mul = num1 * num2 * num3;
                            max = mul;
                        }else{
                            mul = num1 * num2 * num3;
                            if(max < mul){
                                max = mul;
                            }
                        }
                    }
                }
            }
            return max;
        }

    这段代码使用了三重嵌套,计算时间复杂度时如果仍然像二重嵌套一样使用等差数列求解,可能会出现一些难以处理的计算。但是这段算法的本质是把数组中不相同的3个数的每个组合遍历了一遍,所以就用到了概率论中的组合数,C(n,3),从n个数里面随机挑3个组合,得到时间复杂度为O(N^3),由于临时变量都是常数级别的,空间复杂度为O(1)。

    呵呵,这么大,怪不得会超出限制。


    小结 

    做个总结,要想衡量一个算法的优劣是绕不开时间复杂度和空间复杂度的。时间复杂度往往讨论最坏情况,递归调用函数的空间复杂度只与递归深度有关。

    如有补充纠正欢迎留言。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值