【初级数据结构】复杂度知识

本文探讨了代码质量衡量标准中的复杂度概念,包括时间复杂度和空间复杂度。通过实例解析了如何计算时间复杂度,如常数阶、线性、平方及对数时间复杂度,并介绍了空间复杂度的计算。同时,提供了旋转数组、查找数组中缺失数字等算法问题的解决方案及其复杂度分析,强调了优化代码以提高效率的重要性。
摘要由CSDN通过智能技术生成

复杂度

1.背景

当我们在讨论代码的好坏时,什么因素可以衡量?

——算法所耗费的资源和时间

我们把这类因素称为复杂度,显而易见,复杂度是从空间与时间两个维度进行讨论与计算的

2.分类

1)时间复杂度

一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度时间频度。记为T(n)

假设算法的问题规模为n,那么操作单元数量便用函数f(n)来表示

算法执行时间的增长率和f(n)的增长率相同,称为 算法的渐近时间复杂度,简称时间复杂度, 记为 T(n) = O(f(n))

怎样计算时间复杂度呢?又怎样去表示时间复杂度呢?

先来了解时间复杂度的分类

OIP-C
int n = 0;
int m = 0;
m = n + 2;
m++;
//可以看到,这里的几行代码计算了数次,但是时间复杂度却是 O(1)
//这类代码的运算次数并不会随着某个变量的改变而改变,结构并不“复杂”
//总结:常数阶的时间复杂度都为 O(1)
for(int i=0;i<n;i++)
{
    sum += i;
}
//这里的代码进行了 n 个循环,很容易理解时间复杂度为 O(n)
//但如果是没有嵌套的两个 for循环呢?再加上几句独立的运算呢?
//打个比方,程序执行了2n+3次,那么时间复杂度会变成O(2n)吗?答案是否定的,依旧是O(n)

不妨这样理解:在计算时间复杂度时,只保留语句频度的数学表达式中最高阶的变量部分,因为在真正严苛的运算下,n的取值是极大的。在取值极大时,变量的阶越高,其所决定执行次数的权重便更大,所以我们保留最高阶的变量部分,也造就了所谓的渐进表示法

for(i=0;i<n;i++)
{
	for(j=0;j<n;j++)
        m++;
}
//随着循环的逐层嵌套,时间复杂度也会呈指数级别地上升
int i = 1;
while(i<n)
{
    i = i * 2;
}
//在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n
//也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)

其余的类别与已经介绍过的有异曲同工之处,这里就不一一赘述

2)空间复杂度

与时间复杂度也相类似,只不过空间复杂度计算的是临时占用的变量个数

与时间复杂度不同的是,空间复杂度的分类明了,只有两种:

  • 没有变量决定占用的临时空间大小
  • 临时空间根据变量分配

3.例题以及讲解

1 客观题

for(int i=0;i<n;i++)
  for(int j=0;j<n;j++)
    a[i][j]=i*j;
//计算时间复杂度: O(n^2)
//嵌套的循环,简单易懂
 int f ( unsigned int n )
{
    if (n == 0 || n==1) 
      return 1;
    else 
      return n * f(n-1);
}
//计算时间复杂度: O(n)
//一看是个递归,第一反应很难,实际并不是,仔细看看,递归调用n - 1次,每次操作都是一次,所以时间复杂度为n
给定一个整数sum,从有N个有序元素的数组中寻找元素a,b,使得a+b的结果最接近sum,最快的平均时间复杂度是( ?//数组元素有序,所以a,b两个数可以分别从开始和结尾处开始搜,根据首尾元素的和是否大于sum,决定搜索的移动,整个数组被搜索一遍,就可以得到结果,所以最好时间复杂度为n
如果一个函数的内部中只定义了一个二维数组a[3][6],请问这个函数的空间复杂度为(   )
//函数内部数组的大小是固定的,不论函数运行多少次,所需空间都是固定大小的,因此空间复杂度为O(1)

观察上面的几道客观题,不难发现他们几乎都是计算时间复杂度,而这些计算也都是关于基础的循环嵌套以及定义上的知识

2 编写代码

####旋转数组问题

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

看到题目之后,我们能反应出什么思路?

  1. 使用另建的数组来装重新排序过的数组元素

简单粗暴,来看看代码的实现:

void rotate(int* nums, int numsSize, int k)
{
    int newArr[numsSize];
    for (int i = 0; i < numsSize; ++i)
    {
        newArr[(i + k) % numsSize] = nums[i];
    }
    for (int i = 0; i < numsSize; ++i)
    {
        nums[i] = newArr[i];
    }
}

要注意的点就是正确计算后续元素所放置位置的下标

虽然简单粗暴,但是复杂度方面呢?

时间复杂度: O(n)

空间复杂度: O(n)

  1. 反转数组

这是个挺妙的方法,规避了移位的固定思维,将这题的思路变成两次倒置

IMG_0619

看懂了思路之后代码该如何写就很清晰了

void swap(int* a, int* b)
{
    int t = *a;
    *a = *b, *b = t;
}

void reverse(int* nums, int start, int end)
{
    while (start < end)
    {
        swap(&nums[start], &nums[end]);
        start += 1;
        end -= 1;
    }
}

void rotate(int* nums, int numsSize, int k) 
{
    k %= numsSize;
    reverse(nums, 0, numsSize - 1);//倒置整个数组
    reverse(nums, 0, k - 1);//倒置规定的前半部分数组
    reverse(nums, k, numsSize - 1);//倒置后半部分数组
}

这次的复杂度情况又是如何呢?

时间复杂度:O(n)

空间复杂度:O(1)

遗失数字问题

数组nums包含从0n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?

提供两种颇为巧妙的思路

  1. 异或运算

    image-20221023202847675

    明确^运算的特殊性质:

    n ^ n = 0

    n ^ 0 = n

按照题干,可以想到大概的方法:在原先数组中填充0 ~ n,然后在数组中两两不遗漏地进行异或运算,按照上述特殊性质,最后得到的结果就是缺失的数字,代码如下:

public class Solution
{
    public int MissingNumber(int[] nums)
    {
        int xor = 0;
        int n = nums.Length;
        for (int i = 0; i < n; i++) 
        {
            xor ^= nums[i];
        }
        for (int i = 0; i <= n; i++) 
        {
            xor ^= i;
        }
        return xor;
    }
}

来看复杂度:

时间复杂度:O(n)

空间复杂度:O(1)

  1. 求和作差

根据数学知识,我们可以知道0 ~ n的总和为 ( n * ( n + 1 ) ) / 2,我们也可以利用循环来求得数组中所有数字之和,再作差求得缺失的数字,这个方法很是巧妙

int missingNumber(int* nums, int numsSize)
{
    int n = numsSize;
    int sum = 0;
    int total = (n*(n + 1))/2;
    for(int i=0;i<numsSize;i++)
    {
        sum += *(nums + i);
    }
    return total - sum;
}

时间复杂度:O(n)

空间复杂度:O(1)

经过优化的代码,其复杂度也会随之下降,从而提高运行效率,题目虽小,但要牢记思想意义,在每道做过的题目中发现新的、巧妙的方法,这也是一种自我水平的提升。所以我们在做题时应该多加思考,多多推敲细节才行。

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值