通俗理解时间复杂度和空间复杂度

本文详细介绍了算法的时间复杂度和空间复杂度,包括大O符号表示法及常见的复杂度量级,如常数阶、对数阶、线性阶等,并通过具体示例解释了不同量级的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

时间复杂度和空间复杂度

主要是从算法所占用的「时间」和「空间」两个维度去考量。

  • 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
  • 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。

一、时间复杂度
1.「 大O符号表示法 」,即 T(n) = O(f(n)) ;

其中f(n) 表示每行代码执行次数之和,而 O 表示正比例关系,这个公式的全称是:算法的渐进时间复杂度。

例如:

for(i=1; i<=n; ++i)
{
   j = i;
   j++;
}

假设:这段代码每行的的执行时间都是一样的,我们用s来表示,那么第一行代码的执行时间就是1颗粒时间,第三行代码的的执行时间是:n x s ,因为要循环n次,第四行代码的执行时间也是n颗粒时间,所以,这段代码的总执行时间是:(1+n+n)x s,合并一下就是:(1+2n) x s: 所以可得: T(n)=(1+2n) x s 。

从这个公式中可以看出,这个算法的耗时是跟随n的变化而变化的,当n变为无限大的时候,常量1就没有意义了,倍数2也没有意义了。所以就可以简化为 : T(n)=O(n)。

2.常见的时间复杂度量级有:

		常数阶O(1)

		对数阶O(logN)

		线性阶O(n)

		线性对数阶O(nlogN)

		平方阶O(n²)

		立方阶O(n³)

		K次方阶O(n^k)

		指数阶(2^n)

上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。

(1.) 常数阶O(1)

无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如:

int i = 1;
int j = 2;
++i;
j++;
int m = i + j;

(2.) 线性阶O(n)

这个在最开始的示例中就讲解过了,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。 如:

for(i=1; i<=n; ++i)
{
   j = i;
   j++;
}

(3.) 对数阶O(logN)

例如:

int i = 1;
while(i<n)
{
    i = i * 2;
}

(5.) 平方阶O(n²)

如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 平方阶了。

例如:双重for循环

for(x=1; i<=n; x++)
{
   for(i=1; i<=n; i++)
    {
       j = i;
       j++;
    }
}

上面代码可以看到,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么对公式开方以后,就是 x = log2^n ,也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码最终0的时间复杂度为:O(logn) 。

(4.) 线性对数阶O(nlogN)

根据上面的例子,线性对数阶O(nlogN) 就非常容易理解了,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。

for(m=1; m<n; m++)
{
    i = 1;
    while(i<n)
    {
        i = i * 2;
    }
}

如果将其中一层循环的n改成m,那它的时间复杂度就变成了 O(m*n) ,即:

for(x=1; x<=m; x++)
{
   for(i=1; i<=n; i++)
    {
       j = i;
       j++;
    }
}

(6.) 立方阶O(n³)、K次方阶O(n^k)

结合上面的例子,

O(n³)就是在O(n²)基础上再嵌套一层循环,

O(n^k)就是嵌套K次循环。

除此之外,其实还有 平均时间复杂度、均摊时间复杂度、最坏时间复杂度、最好时间复杂度 的分析方法,有点复杂,暂时不做赘述。

二、空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义。
空间复杂度比较常用的有:O(1)、O(n)、O(n²),我们下面来看看:

(1.) 空间复杂度 O(1)

如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)

举例:

int i = 1;
int j = 2;
++i;
j++;
int m = i + j;

代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)

(2.) 空间复杂度 O(n)

我们先看一个代码:

int[] m = new int[n]
for(i=1; i<=n; ++i)
{
   j = i;
   j++;
}

这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)。

<think>嗯,用户问的是Java中如何计算时间复杂度空间复杂度。首先,我需要回忆一下时间复杂度空间复杂度的基本概念,然后结合Java的特点来解释。 时间复杂度主要是看代码执行的时间随数据规模增长的趋势。常见的大O表示法,比如O(1)、O(n)、O(n²)这些。在Java里,分析的时候要关注循环、递归这些结构。比如一个for循环从0到n,那可能就是O(n)的时间复杂度,如果是双重循环的话可能就是O(n²)。还有递归的情况,比如斐波那契数列的递归实现,时间复杂度是O(2^n),因为每次调用会分裂成两个子问题。 那怎么计算呢?可能需要分步骤来,先找出代码中的基本操作,然后看这些操作的执行次数输入规模n的关系。忽略常数项低次项,只保留最高阶的项。比如,如果有两个并列的for循环,每个都是O(n),那么总的是O(n) + O(n) = O(n),而不是O(2n),因为常数系数可以忽略。 空间复杂度的话,关注的是算法运行过程中使用的额外内存空间。同样用大O表示法。比如,如果创建了一个大小为n的数组,那空间复杂度是O(n)。递归的话要考虑调用栈的深度,比如递归深度是n的话,空间复杂度就是O(n)。 用户可能是在学习算法或者准备面试,想知道如何评估自己的代码效率。他们可能已经写过一些Java程序,但不太清楚如何量化分析性能。需要举一些具体的例子,比如循环、递归、动态数组等,来说明不同情况下的复杂度计算。 另外,可能需要强调最好情况最坏情况的区别,比如某些排序算法在不同情况下的时间复杂度不同。但通常我们关注最坏情况或者平均情况。 还要注意,Java中的一些数据结构本身的空间复杂度,比如ArrayList动态扩容,虽然分摊时间复杂度是O(1),但空间可能分配了O(n)的容量。这可能会影响空间复杂度的计算。 可能用户会混淆时间复杂度实际运行时间,需要指出时间复杂度是理论上的增长趋势,不考虑具体硬件的影响。同样,空间复杂度也不考虑具体的字节数,而是看相对于输入规模的量级。 总结的话,要分步骤说明如何分析代码中的循环、递归,确定基本操作的次数,然后用大O表示法简化。空间复杂度则是看变量、数据结构递归栈的使用情况。给出具体例子会有助于理解,比如遍历数组、嵌套循环、递归函数等。</think>在 Java 中,计算时间复杂度空间复杂度是评估算法效率的核心方法。以下是通俗易懂的解释计算方法: --- ### **一、时间复杂度** 表示代码执行时间随数据规模(n)增长的趋势,用大 O 表示法(O(n))描述。 #### **计算步骤:** 1. **找基本操作**:代码中最耗时的重复操作(如循环、递归)。 2. **算执行次数**:用表达式表示执行次数与输入规模 `n` 的关系。 3. **简化表达式**:保留最高阶项,忽略常数低阶项。 #### **常见场景:** - **O(1)**:无循环/递归,直接操作。 ```java int a = 10; // 执行一次 ``` - **O(n)**:单层循环。 ```java for (int i = 0; i < n; i++) { // 循环 n 次 System.out.println(i); } ``` - **O(n²)**:双层嵌套循环。 ```java for (int i = 0; i < n; i++) { // 外层 n 次 for (int j = 0; j < n; j++) { // 内层 n 次 System.out.println(i + j); } } ``` - **O(log n)**:循环中变量按倍数缩减。 ```java for (int i = 1; i < n; i *= 2) { // 执行 log₂(n) 次 System.out.println(i); } ``` - **O(n log n)**:快速排序、归并排序的复杂度。 #### **递归算法**: 递归时间复杂度 = **递归调用次数 × 每次递归的操作次数** 例如斐波那契数列递归(O(2ⁿ)): ```java int fib(int n) { if (n <= 1) return n; return fib(n-1) + fib(n-2); // 每次递归分裂为两次调用 } ``` --- ### **二、空间复杂度** 表示算法运行所需额外内存空间随数据规模(n)增长的趋势。 #### **计算步骤:** 1. **找额外空间**:算法本身占用的内存(不包括输入数据)。 2. **算空间关系**:用表达式表示空间占用与 `n` 的关系。 3. **简化表达式**:保留最高阶项。 #### **常见场景:** - **O(1)**:只使用固定变量。 ```java int a = 10; // 常量空间 ``` - **O(n)**:数组或集合占用线性空间。 ```java int[] arr = new int[n]; // 数组长度为 n ``` - **O(n²)**:二维数组。 ```java int[][] matrix = new int[n][n]; // n × n 的矩阵 ``` - **递归调用栈**: 递归深度 × 每次递归的空间。 例如斐波那契递归(O(n)): ```java int fib(int n) { if (n <= 1) return n; return fib(n-1) + fib(n-2); // 递归深度为 n } ``` --- ### **三、实战技巧** 1. **关注最坏情况**:例如数组遍历时,可能遍历全部元素(O(n))。 2. **忽略常数项**:O(2n) → O(n),O(100) → O(1)。 3. **空间换时间**:哈希表(HashMap)用 O(n) 空间换取 O(1) 查询时间。 4. **Java 集合的复杂度**: - `ArrayList.add()`:均摊 O(1)(动态扩容时 O(n))。 - `HashMap.get()`:平均 O(1),最坏 O(n)(哈希冲突严重时)。 --- ### **示例分析** ```java void findDuplicates(int[] nums) { Set<Integer> seen = new HashSet<>(); // 空间 O(n) for (int num : nums) { // 时间 O(n) if (seen.contains(num)) { // contains() 平均 O(1) System.out.println(num); } else { seen.add(num); // add() 平均 O(1) } } } ``` - **时间复杂度**:O(n)(单层循环,每次操作 O(1))。 - **空间复杂度**:O(n)(HashSet 存储最多 n 个元素)。 --- 通过分析代码中的循环、递归数据结构的空间占用,可以逐步推导出时间空间复杂度。重点是抓住代码中与输入规模 `n` 相关的核心操作!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值