啃算法:01基础知识

39 阅读5分钟

个人认为算法是有一个大的前提是,那就是资源有限。这种情况体现在GC,例如删除数组中的某个元素,然后重新腾空间的情况,或者说链表的这种形式弥补了长度空间不足的情况,而采用了分散存储,提高了资源利用率,当然还有一些内容可以杠,但是没有必要。借用很经典的一句话:时间换空间与空间换时间,这句话很好的描述了算法的前置的逻辑基础。

算法中有两个概念,时间复杂度O(n)和空间复杂度zO(n)。

时间复杂度和空间复杂度都是用来衡量算法性能的指标。它们分别描述了算法的运行速度和所需的内存空间。

时间复杂度描述了算法执行的时间随输入数据规模增长的变化情况。它通常表示为输入数据规模n的函数,用来评估算法在最坏、平均和最好情况下的运行时间。常见的时间复杂度包括常数阶O(1)、线性阶O(n)、平方阶O(n²)、对数阶O(logn)等。

空间复杂度描述了算法在运行过程中所需内存空间随输入数据规模增长的变化情况。它也表示为输入数据规模n的函数,评估算法所需的空间。常见的空间复杂度包括常数阶O(1)、线性阶O(n)、对数阶O(logn)等。

在设计和分析算法时,我们需要考虑时间复杂度和空间复杂度的平衡。有时,为了降低时间复杂度,可能会增加空间复杂度;反之,为了降低空间复杂度,可能会增加时间复杂度。因此,在实际应用中,我们需要根据具体需求和资源限制来选择合适的算法。

时间复杂度

O(1)

时间复杂度O(1)表示算法的时间复杂度是常数级别的,也就是说,无论输入数据规模N有多大,算法的时间复杂度都不会发生改变。具体来说,这意味着该运算在执行时间不会随着输入模型的增加而增加,而是始终保持在一个常数时间,例如hash算法就是典型的O(1)时间复杂度,无论数据规模多大,都可以在一次计算后找到目标。

常见的时间复杂度是O(1)的例子包括:

  1. 访问数据或链表中的一个元素,无论是通过索引访问数组中的元素还是通过指针访问链表中的元素,都可以在常数时间内完成。
  2. 获取一个对象的属性,因为对象属性在内存中的固定位置。
  3. hash表或hash映射的查找。
  4. 调用一个空的方法或者不执行任何操作。
  5. 使用常量进行运算,加减乘除。

O(n) 常数阶

for(int i=0;i<n;i++){

a++;

}

平方阶O(n²)

for(int i=0;i<n;i++){

for(int j=0;j<m;j++){

num++

}

}

对数阶O(logn)

  1. 二分查找(Binary Search):二分查找是一种在有序数组中查找特定元素的搜索算法。每次比较数组的中间元素与目标值,根据比较结果将搜索范围缩小一半。因此,其递归深度为logn,空间复杂度为O(logn)。

  2. 二分图的最大匹配(Hopcroft-Karp算法):Hopcroft-Karp算法是一种求解二分图最大匹配的算法。它通过递归地寻找增广路径来逐步增加匹配数,每次递归都会将问题规模减小一半,因此其空间复杂度为O(logn)。

空间复杂度

每一个算法所编写的程序,运行过程中都需要占用大小不等的存储空间。

  1. 程序代码本身所占有的存储空间。
  2. 程序中如果需要输入输出也会占用一定的空间。
  3. 程序运行过程中也需要临时申请更多的存储空间。

搜先程序自身所占用空间取决于代码量,如果要压缩这部分空间就要求我们在实现功能的同时,尽可能的编写足够短的代码。程序运行输入输出 数据,往往。由要解决的问题而定,即使使用算法不同,程序输入输出所占用的存储空间也是相近的。事实上,对算法的空间复杂度影响最大的,往往是程序运行过程中所申请的临时存储空间,不同于算法所编写的程序,其运行时申请的临时存储空间通常会有较大的不同。

我们知道,在调用函数的时候,会创建栈帧的,简单的说,我们每调用一次函数,就会为调用的函数创建一块空间,在计算递归算法的空间复杂度时,我们可以认为每次函数调用时都会创建常数个变量,那么影响我们算法空间复杂度的就是我们调用递归的次数。递归算法的空间复杂度通常是递归的深度。

空间复杂度一般只有两种情况:

  1. 创建了常数个变量

  2. 创建了N个变量

常数阶O(1)

var num=0

for (i in 1..10){

num=i

}

可以看到,这个循环对空间复杂度完全不影响,即使是空的循环,他空间复杂度也是常数。

线性阶O(n)

val array= mutableListOf()

for (i in 1..10){

array+=i

}

那么剩下的几个,也就好理解了。