【初阶数据结构】——时间复杂度和空间复杂度详解(C描述)(二)

简介: 【初阶数据结构】——时间复杂度和空间复杂度详解(C描述)(二)

3. 空间复杂度

3.1 空间复杂度的概念

空间复杂度又是什么呢?

空间复杂度也是一个问题规模n的函数,是对一个算法在运行过程中临时占用存储空间大小的量度

空间复杂度不是计算程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数

空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法

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

3.2 常见空间复杂度计算举例

例1.常数次循环

先来看一个简单的:

void Func4(int N) {
  int count = 0;
  for (int k = 0; k < 100; ++k)
  {
    ++count;
  }
  printf("%d\n", count);
}

它的空间复杂度是多少呢?

O(1)

它里面是不是就创建了两个变量啊,count 和k ,那我们说了空间复杂度也使用大O的渐进表示法计算,申请两个变量的空间,常数个,那就是O(1)了。

注意:虽然循环中int k每次循环都创建,但它还是算一个。

例2.冒泡排序

第二个,冒泡排序的空间复杂度:

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

还是O(1)。

我们分析一下,它里面是不是就新定义了3个变量,额外申请了3个变量的空间啊。

那函数参数不算吗?

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

那3个变量,还是O(1)。

例3.返回斐波那契数列的前n项的数组

第三个:

// 返回斐波那契数列的前n项
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;
}

答案是O(N)

我们它里面动态申请了一个数组,n + 1个元素,另外还有几个变量,像指针变量long long* fibArray,for循环中还有个int i = 2,但还是常数个N+几,我们可以不用管,所以就是O(N)

例4.递归求阶乘

再看一个:

long long Fac(size_t N) {
  if (N == 1)
    return 1;
  return Fac(N - 1) * N;
}

这个是多少?

O(N)

递归调用了N次,开辟了N个函数栈帧(最后返回时才会逐一销毁),每个栈帧使用了常数个空间。空间复杂度为O(N)

例5. 递归求斐波那契第N项

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

该算法的时间复杂度我们求过了是O(2^N),那空间复杂度呢?

O(2^N)吗,不是的,它的空间复杂度是O(N)

为什么呢?

时间是一去不复返的,是累加的,但是空间是可以重复利用的。

什么意思呢?

93d6b75aba214f9eb682e7cb4aef01c7.png

这是我们计算时间复杂度是分析的图,它递归调用了这么多次,但是,这么多分支,它们进行递归,开辟函数栈帧,是同时进行吗?

不是的。

它们是一个分支,一个分支按照先后顺序进行的。

Fib(N )=Fib(N - 1) + Fib(N - 2)

Fib(N - 1)递归调用完,才会开始Fib(N - 2),它们会重复利用同一块空间。

1c5295b4239643058f5153001cd95ae0.png

最长的那个分支同时建立N个函数栈帧,所以空间复杂度为O(N)

4. 常见复杂度对比

f2b3f772e51d4b338b743bf4bfcc9b21.png

5. 复杂度的oj练习

5.1 消失的数字

链接: link

链接放这里,大家可以自己做一下。

b970d004fa5f42e2b8ea7ec0d49c237e.png

这里给两种解法:

1. 0到n求和减去数组元素之和

2. 让0和0到n异或,异或的结果和数组元素异或,最终得到的结果就是缺失的那个数字。

原理:0和任何数异或结果还是这个数,两个相同的数字异或结果为0。

上代码:

int missingNumber(int* nums, int numsSize){
    //思路一:0到n求和减去数组元素和
    // int i=0;
    // int sum=0;
    // for(i=1;i<=numsSize;i++)
    // {
    //     sum+=i;
    // }
    // int j=0;
    // for(j=0;j<numsSize;j++)
    // {
    //     sum-=nums[j];
    // }
    // return sum;
    //思路二:单身狗问题
    int i=0;
    int x=0;
    //x=0和0到n异或
    for(i=1;i<=numsSize;i++)
    {
        x^=i;
    }
    //x和数组元素异或
    int j=0;
    for(j=0;j<numsSize;j++)
    {
        x^=nums[j];
    }
    return x;
}

5.2 轮转数组

链接: link

72bd81f37f984d0188ab8a4f3e9a5d32.png

425697df8ced4868b706c6c42512e6fa.png

还是给两种解法:


1. 再创建一个同样大小的数组,把需要轮转的元素放在数组前面,然后把剩余元素放到数组后面,再拷贝到原数组中。

另外需要注意,如果N个元素的数组,你轮转N次是不是就还和原来的一样,而且K如果太大,大于 题中的numSize,下标为负,还会越界访问,所以加上一句k%=numsSize;

db9dae21d4a54dd2af5c3aa0e1a094c8.png

void rotate(int* nums, int numsSize, int k)
{
    k%=numsSize;
    int arr[numsSize];
    int i=0;
    int j=0;
    //需要轮转的元素放在数组前面
    for(i=numsSize-k;i<numsSize;i++)
    {
        arr[j]=nums[i];
        j++;
    }
    //剩余元素放到数组后面
    for(i=0;i<numsSize-k;i++)
    {
        arr[j]=nums[i];
        j++;
    }
    //拷贝到原数组中
    j=0;
    for(i=0;i<numsSize;i++)
        {
            nums[j]=arr[i];
            j++;
        }
}

先将前n-k个元素进行逆序,然后将剩余元素进行逆序,最后再逆序整个数组。

c6f0ada2fa644ec28a74c13c34ef4e07.png

void reverse(int* start, int* end)
{
    while (start < end)
    {
        int tmp = *start;
        *start = *end;
        *end = tmp;
        start++;
        end--;
    }
}
void rotate(int* nums, int numsSize, int k)
{
    //向右轮转numsSize次相当于没轮转
    k %= numsSize;
    reverse(nums, nums + numsSize - 1 - k);
    reverse(nums + numsSize - k, nums + numsSize - 1);
    reverse(nums, nums + numsSize - 1);
}

好了,以上就是这篇文章的全部内容,欢迎大家指正!!!

9605c43643bd4270a9d602dd48261124.png

目录
相关文章
|
1月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
70 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
1月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
26 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
1月前
|
机器学习/深度学习 存储 算法
【初阶数据结构】算法效率大揭秘 | 时间与空间复杂度的深度剖析
【初阶数据结构】算法效率大揭秘 | 时间与空间复杂度的深度剖析
|
1月前
|
算法
[数据结构] -- 时间复杂度和空间复杂度
[数据结构] -- 时间复杂度和空间复杂度
15 0
|
3月前
|
存储 算法
【数据结构】——时间复杂度与空间复杂度
【数据结构】——时间复杂度与空间复杂度
|
5月前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-2
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
5月前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-1
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
20天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
103 9
|
11天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
19 1
|
14天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。