时间复杂度和空间复杂度

系列文章目录

🎈 🎈 我的优快云主页:OTWOL的主页,欢迎!!!👋🏼👋🏼
🎉🎉我的C语言初阶合集C语言初阶合集,希望能帮到你!!!😍 😍
🔍🔍我的C语言进阶合集我的C语言进阶合集,期待你的点击!!!🌈🌈
🎉🎉我的数据结构与算法合集数据结构与算法合集,点进去看看吧!!! 🎊🎊


前言

各位博友,大家好!👋 今天为大家送上算法分析中两个核心概念的总结——时间复杂度和空间复杂度🔍。


一、衡量算法的效率

算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。
因此衡量一个算法的好坏,一般是从 时间和空间 两个维度来衡量的,即 时间复杂度 和 空间复杂度
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。

二、时间复杂度

(1)时间复杂度的概念

算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。
算法中的 基本操作的执行次数,为算法的时间复杂度。

(2)大O的渐进表示法

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

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

(3)算法的时间复杂度中的最好、平均和最坏情况

最坏情况:任意输入规模的最大运行次数(上界);
平均情况:任意输入规模的平均运行次数;
最好情况:任意输入规模的最小运行次数(下界)。

  • 例如:

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

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

三、空间复杂度

(1)空间复杂度的概念

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
空间复杂度算的是 变量的个数
空间复杂度计算规则基本跟 时间复杂度 类似,也使用大O渐进表示法。

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

四、常见的时间复杂度和空间复杂度分析

实例 1:

#include<stdio.h> // 包含标准输入输出库,用于 printf 函数

void Func() // 定义一个名为 Func 的函数
{
    int count = 0; // 初始化一个名为 count 的变量,用于计数

    // 循环,k 从 0 迭代到 2倍的 N( N 是一个未定义的常量或宏,需要在其他地方定义)
    for (int k = 0; k < 2 * N; ++k)
    {
        ++count; // 每次循环增加 count 的值
    }

    int M = 10; // 初始化一个名为 M 的变量,并赋值为 10
    while (M--) // 当 M 大于 0 时,执行循环体,每次循环后 M 减 1
    {
        ++count; // 每次循环增加 count 的值
    }

    printf("%d\n", count); // 打印 count 的值,并换行
}

时间复杂度:

实例 1 基本操作执行了 2N+10 次,通过推导 大O阶方法知道,时间复杂度为 O(N)

空间复杂度:

实例 1 使用了常数个额外空间,所以空间复杂度为 O(1)

实例 2:

#include<stdio.h> // 包含标准输入输出库,用于 printf 函数

// 定义一个名为 Func 的函数,接受两个整型参数 N 和 M
void Func(int N, int M)
{
    int count = 0; // 初始化一个名为 count 的变量,用于计数

    // 第一个循环,k 从 0 迭代到 M-1
    for (int k = 0; k < M; ++k)
    {
        ++count; // 每次循环增加 count 的值
    }

    // 第二个循环,k 从 0 迭代到 N-1
    for (int k = 0; k < N; ++k)
    {
        ++count; // 每次循环增加 count 的值
    }

    printf("%d\n", count); // 打印 count 的值,并换行
}

时间复杂度:

实例 2 基本操作执行了 M+N 次,有两个未知数M和N,时间复杂度为 O(N+M)

空间复杂度:

实例 2 使用了常数个额外空间,所以空间复杂度为 O(1)

实例 3:

#include<stdio.h> // 包含标准输入输出库,用于 printf 函数

// 定义一个名为 Func 的函数,接受一个整型参数 N
void Func(int N)
{
    int count = 0; // 初始化一个名为 count 的变量,用于计数

    // 循环,k 从 0 迭代到 99(共 100 次)
    for (int k = 0; k < 100; ++k)
    {
        ++count; // 每次循环增加 count 的值
    }

    printf("%d\n", count); // 打印 count 的值,并换行
}

时间复杂度:

实例 3 基本操作执行了 100 次,通过推导大O阶方法,时间复杂度为 O(1)

空间复杂度:

实例 3 使用了常数个额外空间,所以空间复杂度为 O(1)

实例 4:

#include<stdio.h> // 包含标准输入输出库,用于可能的打印功能
#include<assert.h> // 包含断言库,用于检查程序逻辑的正确性

// 定义一个名为 BubbleSort 的函数,用于对整数数组进行冒泡排序
// 参数 a 是指向整数数组的指针,n 是数组中元素的数量
void BubbleSort(int* a, int n)
{
    assert(a); // 断言 a 不为空,如果 a 为空,则触发 assertion failure,程序会终止

    // 外层循环,控制排序的轮数,从 n 到 1
    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]); // 调用 Swap 函数交换两个元素
                exchange = 1; // 发生了交换,将 exchange 标记为 1
            }
        }
        if (exchange == 0) // 如果这一轮没有发生交换,说明数组已经有序,可以提前结束排序
        {
            break;
        }
    }
}

时间复杂度:

最坏情况下(数组完全逆序),内层循环每轮会执行 n-1、n-2、...、1次
所以总执行次数是等差数列求和:(n-1) + (n-2) + ... + 1 = n(n-1)/2
因此,最坏情况下的时间复杂度为O(n^2)

在最好的情况下(数组已经是排序好的),exchange 始终为 0,因此外层循环只会执行一次,时间复杂度为O(n)

空间复杂度:

实例 4 使用了常数个额外空间,所以空间复杂度为 O(1)

实例 5:

#include<assert.h> // 包含断言库,用于检查程序逻辑的正确性

// 定义一个名为 BinarySearch 的函数,用于在已排序的整数数组中查找特定值 x
// 参数 a 是指向整数数组的指针,n 是数组中元素的数量,x 是要查找的值
int BinarySearch(int* a, int n, int x)
{
    assert(a); // 断言 a 不为空,如果 a 为空,则触发 assertion failure,程序会终止

    int begin = 0; // 初始化开始索引为 0
    int end = n - 1; // 初始化结束索引为数组最后一个元素的索引

    // 循环直到 begin 和 end 相遇或交叉
    while (begin <= end)
    {
        int mid = begin + ((end - begin) >> 1); // 计算中间索引,
        //使用右移位运算符(右移其实就是将该数字大小除以 2)以获得更均匀的分布。
        //等价于 mid = (1/2)(begin + end)
        
        if (a[mid] < x) // 如果中间元素小于 x,则在右半区间继续查找
            begin = mid + 1; // 更新开始索引为中间索引的下一个位置
        else if (a[mid] > x) // 如果中间元素大于 x,则在左半区间继续查找
            end = mid - 1; // 更新结束索引为中间索引的前一个位置
        else
            return mid; // 如果中间元素等于 x,返回中间索引,表示找到了 x
    }
    return -1; // 如果循环结束仍未找到 x,返回 -1 表示查找失败
}

时间复杂度:

二分查找算法是基于排序数组的查找算法,它通过 重复地将搜索区间减半 来定位目标值。
对于数组a的长度n,二分查找的循环次数与对数组进行对数分割的次数成正比。
具体来说,在每次迭代中,查找区间缩小一半。设k为循环次数,
那么满足以下关系:
2^k = n 取对数得到: k = log(n)

实例 5 基本操作执行最好1次,最坏 O(logN) 次,时间复杂度为O(logN)

补充logN 在算法分析中表示是底数为 2对数为 N。有些地方会写成 lgN

空间复杂度:

实例 5 使用了常数个额外空间,所以空间复杂度为 O(1)

实例 6:

// 定义一个名为 Fac 的函数,用于计算给定非负整数 N 的阶乘
// 参数 N 是 size_t 类型的非负整数,表示要计算阶乘的数
long long Fac(size_t N)
{
    // 如果 N 为 0,根据阶乘的定义,0 的阶乘是 1,直接返回 1
    if (0 == N)
        return 1;

    // 如果 N 不为 0,递归调用 Fac 函数计算 (N-1) 的阶乘,并将结果乘以 N
    // 这是递归算法的典型应用,通过不断减少问题的规模来解决问题
    return Fac(N - 1) * N;
}

时间复杂度:

实例 6 通过计算分析发现基本操作递归了 N 次,时间复杂度为O(N)

空间复杂度:

实例 6 递归调用了 N 次,开辟了 N 个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)

五、常见复杂度对比

13140(1)常数阶
3n+40(n)线性阶
3n^2 + 4*n + 50(n^2)平方阶
3log n + 50(log n)对数阶
3nlog n + 50(nlog n)nlog n 阶
n^3 + n^2 + 60(n^3)立方阶
2^n0(2^n)指数阶
  • 图片展示:


END

每天都在学习的路上!
On The Way Of Learning

评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OTWOL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值