初步掌握数据结构——时间复杂度与空间复杂度

目录

一、算法的复杂度

二、时间复杂度

时间复杂度的概念

大O的渐近表示法

时间复杂度计算举例

三、空间复杂度

空间复杂度的概念

空间复杂度的计算举例

四、常见复杂度对比


初步掌握数据结构——时间复杂度与空间复杂度_第1张图片

 

数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。而所谓算法,简单来说就是一系列计算步骤,用来将输入数据转化成输出结果。

那么如何来衡量一个算法的好坏呢?这就要用到本节所讲述的时间复杂度与空间复杂度了。

一、算法的复杂度

回到我们的问题,如何衡量一个算法的好坏?只是看它的运行时间吗?显然,同样的算法在不同计算能力的计算机上的运行时间截然不同,评价一个算法需要统一的尺标。总的来说,这个尺标大体上可以分为两个维度,即时间与空间

时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。但由于计算机发展迅速,计算机的存储能力已经到了较高的水平,如今已不需要再特别关注一个算法的空间复杂度了。

二、时间复杂度

时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。如前文所诉,这个具体时间从理论上来说是无法衡量的,所以才有了时间复杂度这个分析方式。一个算法所花费的时间于其中语句的执行次数成正比,算法中的基本操作的执行次数,即为算法的时间复杂度

例如计算下列代码中++count执行的次数:

void Func1(int N)
{
    int count = 0;
    for (int i = 0; i < N; ++i)
    {
        ++count;
    }

    for (int k = 0; k < 2 * N; ++k)
    {
        ++count;
    }

    int M = 10;
    while (M--)
    {
        ++count;
    }
}

经过简单的计算即可得到F(N) = N^2 + 2*N + 10

而随着N的增大,后面的两项对结果的影响逐渐减小

在计算时间复杂度的过程中,并不要求计算出精确的执行次数,只要明确大概的执行次数即可。因此我们选用大O的渐进表示法描述时间复杂度。

大O的渐近表示法

大O符号是用于描述函数渐进行为的数学符号,有以下规律:

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

使用大O的渐进表示法后,Func1的时间复杂度为:O(N^2)

在某些算法中,时间复杂度存在多种情况

例如:在一个长度为N的数组中查找一个数据x,可能只需要一次,也可能需要N次。实际中往往关注算法的最坏运行情况,因此该算法的时间复杂度为O(N)

时间复杂度计算举例

计算BubbleSort的时间复杂度

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

该算法中每轮比较的次数为一个等差数列,即N-1、N-2、N-3……1;因此总的执行次数最坏为N*(N-1)/2,即等差数列求和,化简得时间复杂度为O(N^2)

计算斐波那契递归Fib的时间复杂度

计算递归算法的时间复杂度:递归次数*每次递归调用次数

long long Fib(size_t N)
{
    if (N < 3)
    {
        return 1;
    }

    return Fib(N - 1) + Fib(N - 2);
}

初步掌握数据结构——时间复杂度与空间复杂度_第2张图片

 该递归的调用如同一颗“二叉树”,由于一些分支提前调用结束,出现了空白区域,将其中的次数设为x次,不看这块空白区则域每一层的调用次数为2^n次,因此总的次数为等比数列求和,即为2^N-1。此时减去之前多加的x次,即得到2^N-1-x次。代入时间复杂度的运算法则可知时间复杂度为O(2^N)

三、空间复杂度

空间复杂度的概念

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

空间复杂度不是程序占用了多少bytes的空间,而是统计变量的个数。空间复杂度的计算规则与时间复杂度类似,也采用大O的渐进表示法

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

空间复杂度的计算举例

计算斐波那契递归Fib的空间复杂度

计算递归的空间复杂度需要看递归的深度

long long Fib(size_t N)
{
    if (N < 3)
    {
        return 1;
    }

    return Fib(N - 1) + Fib(N - 2);
}

初步掌握数据结构——时间复杂度与空间复杂度_第3张图片

模型图与之前计算时间复杂度的相同,那么时间复杂度也是O(2^N)吗?

需要注意时间的消耗是一去不返的,而空间的占用是可以回收的。因此,计算该算法的空间复杂度,只需要统计最长支路的深度即可(其它支路在使用内存时会使用之前支路调用完成后释放的内存),所以,本算法的时间复杂度为O(N)

 

四、常见复杂度对比

一般算法常见的复杂度如下:

112233 O(1) 常数阶
2n+1 O(n) 线性阶
2n^2+2n+1 O(n^2) 平方阶
log(2)n+1 O(logn) 对数阶
n+log(2)n+1 O(nlogn) nlogn阶
n^3+2n^2+2 O(n^3) 立方阶
2^n O(2^n) 指数阶

评价算法的好坏,一般依照下面的顺序(好的在前)来比较:

O(1)、O(logn)、O(n)、O(nlogn)、O(n^2)、O(2^n)、O(n!)

你可能感兴趣的:(大师之路(数据结构),c语言,c++)