问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

时间复杂度和空间复杂度详解

创作时间:
作者:
@小白创作中心

时间复杂度和空间复杂度详解

引用
CSDN
1.
https://m.blog.csdn.net/qq_50907107/article/details/144267691

算法的时间复杂度和空间复杂度是衡量算法效率的两个重要指标。时间复杂度关注算法的运行速度,而空间复杂度则关注算法所需的额外存储空间。本文将详细介绍这两个概念,并通过具体代码示例展示如何计算常见算法的复杂度。

1.什么是时间复杂度和空间复杂度?

1.1算法效率

算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被称作空间复杂度。时间复杂度主要衡量的是一个算法的运行速度;而空间复杂度主要衡量一个算法所需要的额外空间,在计算机发展的早期,计算机的存储容量很小,所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度,所以我们如今已经不需要再特别关注一个算法的空间复杂度。

1.2时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。

1.3空间复杂度的概念

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。

2.如何计算常见算法的时间复杂度?

2.1大O的渐进表示法

// 计算一下Func1基本操作执行了多少次?
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;
    }
    printf("%d\n", count);
}
int main()
{
    Func1(10);	// F(N) = N^2 + 2*N + 10; N = 10
    return 0;
}

Func1执行的基本操作次数:
F(N) = N^2 + 2*N + 10
N = 10 F(N) = 130
N = 100 F(N) = 10210
N = 1000 F(N) = 1002010

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

推导大O阶方法:

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

使用大O的渐进表示法以后,Func1的时间复杂度为:
O(N^2)
N = 10 F(N) = 100
N = 100 F(N) = 10000
N = 1000 F(N) = 1000000

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界);
平均情况:任意输入规模的期望运行次数;
最好情况:任意输入规模的最小运行次数(下界);

例如:在一个长度为N数组中搜索一个数组x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)。

2.2常见的时间复杂度计算举例

// 实例1:计算Func2的时间复杂度
void Func2(int N)
{
    int count = 0;
    for (int k = 0; k < 2 * N; k++)	// 2N次
    {
        ++count;
    }
    int m = 10;	// 10次
    while (m--)
    {
        ++count;
    }
    printf("%d\n", count);
}
/* 基本操作次数:F(N) = 2N + 10 ;时间复杂度O(N)*/

// 实例2:计算Func3的时间复杂度
void Func3(int N, int M)
{
    int count = 0;
    for (int k = 0; k < M; k++)	// M次
    {
        ++count;
    }
    for (int k = 0; k < N; k++)	// N次
    {
        ++count;
    }
    printf("%d\n", count);
}
/* 基本操作次数:F(N) = M + N ;时间复杂度O(M+N)*/

// 实例3:计算Func4的时间复杂度
void Func4(int N)
{
    int count = 0;
    for (int k = 0; k < 100; k++)	// 100次
    {
        ++count;
    }
    printf("%d\n", count);
}
/* 基本操作次数:F(N) = 100 ;但是是常数次,所以时间复杂度O(1)*/

// 实例4:计算strchr的时间复杂度
const char* strchr(const char* str, char ch)
{
    while (*str != '\0')
    {
        if (*str == ch)	// 最好情况是:1;最坏情况是:N
            return str;
        ++str;
    }
    return NULL;
}
/* 时间复杂度一般看最坏的情况:O(N) */

// 实例5:计算Bubble_sort的时间复杂度
void Bubble_sort(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,内层所需要循环比较的次数为(n-1)、(n-2)…1
   由等差数列求和得时间花销为:[ n(n-1) ] / 2;所以最优的情况时间复杂度为:O( n^2 )。
   
   最差的情况:
   也就是开始的时候元素是逆序的,那么每一次排序都要交换两个元素,则时间花销为:[ 3n(n-1) ] / 2;
  (其中比上面最优的情况所花的时间就是在于交换元素的三个步骤);
   所以最差的情况下时间复杂度为:O( n^2 );
 */

// 实例6:计算Binary_Search的时间复杂度
int Binary_Search(int* a, int n, int x)
{
    assert(a);
    int begin = 0;
    int end = n;
    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;
}
/* 最好的情况:1次;最坏的情况:logN次(以2为底);时间复杂度:O(logN) */

// 实例7:计算阶乘递归Factorial的时间复杂度
long long Factorial(size_t N)
{
    return N < 2 ? N : Factorial(N - 1) * N;
}
/* 递归了N次,时间复杂度:O(N);  */

复杂度对比:
参考:详解算法的各种复杂度的差别有多大(带图) - 立航 - 博客园 (cnblogs.com)

3.如何计算常见算法的空间复杂度?

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。

// 实例1:计算Bubble_sort的空间复杂度
void Bubble_sort(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;
    }
}
/* 使用了常数个额外空间,空间复杂度:O(1) */

![](https://wy-static.wenxiaobai.com/chat-rag-image/10101569099445129122)

// 实例2:计算Fibonacci的空间复杂度
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;
}
/* 动态开辟了N个空间,空间复杂度:O(N) */

// 实例3:计算阶乘递归Factorial的时间复杂度
long long Factorial(size_t N)
{
    return N < 2 ? N : Factorial(N - 1) * N;
}
/* 递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间,空间复杂度:O(N); */
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号