• 数据结构前言(空间复杂度)


    1.空间复杂度

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

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

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

    1. // 计算BubbleSort的空间复杂度?
    2. void BubbleSort(int* a, int n)
    3. {
    4. assert(a);
    5. for (size_t end = n; end > 0; --end)
    6. {
    7. int exchange = 0;
    8. for (size_t i = 1; i < end; ++i)
    9. {
    10. if (a[i-1] > a[i])
    11. {
    12. Swap(&a[i-1], &a[i]);
    13. exchange = 1;
    14. }
    15. }
    16. if (exchange == 0)
    17. break;
    18. }
    19. }

    O(1)


    我们理解空间复杂度就可以用这个函数为了完成这个功能所额外开辟的空间

    因为是初始值,所以指针a和n不算是额外开辟的(必须要有他们才能开始执行函数)

    而这三个就是额外创建的变量所以

    O(3)->O(1)

    1. // 计算Fibonacci的空间复杂度?
    2. // 返回斐波那契数列的前n项
    3. long long* Fibonacci(size_t n)
    4. {
    5. if(n==0)
    6. return NULL;
    7. long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
    8. fibArray[0] = 0;
    9. fibArray[1] = 1;
    10. for (int i = 2; i <= n ; ++i)
    11. {
    12. fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
    13. }
    14. return fibArray;
    15. }

    O(n)


    O(n+3)->O(n)

    大多数空间复杂度为O(1)或O(n)

    1. // 计算阶乘递归Fac的空间复杂度?
    2. long long Fac(size_t N)
    3. {
    4. if(N == 0)
    5. return 1;
    6. return Fac(N-1)*N;
    7. }

    O(n)


    因为函数栈帧也算是空间的开辟 而这里函数开辟了n块空间

    如果用的是for循环,就只需要O(1)

    1. // 计算斐波那契递归Fib的时间复杂度?
    2. long long Fib(size_t N)
    3. {
    4. if(N < 3)
    5. return 1;
    6. return Fib(N-1) + Fib(N-2);
    7. }

    O(n)


    简单理解就是调用递归函数时,会先从下图的紫框的顺序向下调用函数,而调用这些函数的同时,在函数右边的的还会使用同一块空间,所以,真正开辟的空间是O(n),调用完函数后就会销毁,所以不是累加O(n^2)

    2. 常见复杂度对比

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

    3.进阶题目练习

    189. 轮转数组 - 力扣(LeetCode)

    给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

    示例 1:

    输入: nums = [1,2,3,4,5,6,7], k = 3
    输出: [5,6,7,1,2,3,4]
    解释:
    向右轮转 1 步: [7,1,2,3,4,5,6]
    向右轮转 2 步: [6,7,1,2,3,4,5]
    向右轮转 3 步: [5,6,7,1,2,3,4]
    

    示例 2:

    输入:nums = [-1,-100,3,99], k = 2
    输出:[3,99,-1,-100]
    解释: 
    向右轮转 1 步: [99,-1,-100,3]
    向右轮转 2 步: [3,99,-1,-100]

    提示:

    • 1 <= nums.length <= 105
    • -231 <= nums[i] <= 231 - 1
    • 0 <= k <= 105

    3.1 方法一(暴力求解)

    通过暴力求解直接将数组最后一位保存在tmp里,前面的数组向后移动1位,循环k次

    方法直接但是时间复杂度不符合要求,最差的情况是要交换n-1次,当k=n时数组旋转不变。

    3.2 方法二(牺牲空间换时间)

    我们翻转后可以得到规律:当我们轮转3次,,前4个数组(7-3)移动到了后面,而后三个数组(k)移动到了前面,我们就可以拷贝数组到一个新的数组里去。再把他拷贝回去

    实现:

    1. void rotate(int* nums, int numsSize, int k) {
    2. int* tmp = (int*)malloc(sizeof(int) * numsSize); //创建新的数组
    3. int n = numsSize; //存放n的值好进行运算
    4. k%= n; //旋转次数等于数组大小等于没有旋转
    5. memcpy(tmp, nums + n - k, sizeof(int) * k); //后n-k=4的值存放在tmp前面
    6. memcpy(tmp+k, nums, sizeof(int) * (n-k)); //前k=3的值存放在tmp(n-k)=4的位置
    7. memcpy(nums, tmp, sizeof(int) * n); //拷贝回去覆盖

    3.3 方法三(前交换,后交换,整体交换)

    找规律,正常情况不容易想出来

    1. void reveune(int* a, int left, int right)
    2. {
    3. while (left < right)
    4. {
    5. int tmp = a[left];
    6. a[left] = a[right];
    7. a[right] = tmp;
    8. left++;
    9. right--;
    10. }
    11. }
    12. void rotate(int* nums, int numsSize, int k)
    13. {
    14. int n = numsSize;
    15. k %= n;
    16. reveune(nums, 0, n - k-1);
    17. reveune(nums, n-k, n-1);
    18. reveune(nums, 0, n-1 );
    19. }

  • 相关阅读:
    GPS定位与IP地址有什么区别?
    什么密码,永远无法被黑客攻破?
    day35 XSS跨站&反射&存储&DOM&盲打&劫持
    工程(十四)——ubuntu20.04 PL-VINS
    Kafka3.0.0版本——消费者(自动提交 offset)
    [C/C++] 数据结构 链表OJ题:相交链表(寻找两个链表的相交起始结点)
    Android WebSocket长连接的实现
    抑制肯定响应消息指示位(SPRMIB)
    R语言Meta分析核心技术
    数据结构(递归,链表实现递归)
  • 原文地址:https://blog.csdn.net/CatShitK/article/details/134321945