当前位置: 首页 > article >正文

深入理解算法效率:时间复杂度与空间复杂度

目录

引言

一、算法效率的基础

二、时间复杂度

1.概念

2.常见类型

1.O(1) — 常数阶 

2.O(n) — 线性阶

3.O(n^2) — 平方阶

4.O(2^𝑛) — 指数阶

5.O(log 𝑛) — 对数阶 

3.总结 

 三、空间复杂度

1.概念

2.常见类型

1.O(1) — 常数阶

2.O(n) — 线性阶

3.O(n^2) — 平方阶

四、总结


引言

在现代计算机科学和编程中,算法的效率至关重要。算法效率不仅影响程序的运行时间,还直接关系到程序的内存使用情况。为了评估和优化算法,我们常用两个主要指标:时间复杂度和空间复杂度。本文将详细介绍这两个概念,并通过C语言示例来解释它们的实际应用。

一、算法效率的基础

在算法设计中,我们先后追求以下两个层面的目标。

1. 找到问题解法:算法需要在规定的输入范围内可靠地求得问题的正确解。

2. 寻求最优解法:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。

也就是说,在能够解决问题的前提下,算法效率已成为衡量算法优劣的主要评价指标,它包括以下两个维度。

时间效率算法运行速度的快慢。

空间效率算法占用内存空间的大小。

简而言之,我们的目标是设计“既快又省”的数据结构与算法时间效率和空间效率的评估可以帮助我们选择合适的算法来处理特定问题,并优化程序性能。时间复杂度和空间复杂度是用于衡量这两个方面的关键指标。

二、时间复杂度

1.概念

时间复杂度(Time Complexity)用来衡量算法执行所需时间如何随着输入规模的增长而变化。它帮助我们评估算法在处理大数据量时的表现。时间复杂度通常用大O符号表示,描述了算法在最坏情况下的运行时间。

O的渐进表⽰法
⼤O符号(Big O notation):是⽤于描述函数渐进⾏为的数学符号
💡 推导⼤O阶规则
1. 时间复杂度函数式 T(N) 中,只保留最⾼阶项,去掉那些低阶项,因为当 N 不断变⼤时,
低阶项对结果影响越来越⼩,当 N ⽆穷⼤时,就可以忽略不计了。
2. 如果最⾼阶项存在且不是 1 ,则去除这个项⽬的常数系数,因为当 N 不断变⼤,这个系数
对结果影响越来越⼩,当 N ⽆穷⼤时,就可以忽略不计了。
3. T(N) 中如果没有 N 相关的项⽬,只有常数项,⽤常数 1 取代所有加法常数。

 

2.常见类型

1.O(1) — 常数阶 

常数阶时间复杂度指的是算法的运行时间与输入数据大小 𝑛 无关,即不随着 𝑛 的变化而变化。

在以下函数中,尽管操作数量 size 可能很大,但由于其与输入数据大小 𝑛 无关,因此时间复杂度仍为 𝑂(1)
/* 常数阶 */
int constant(int n) {
int count = 0;
int size = 100000;
int i = 0;
for (int i = 0; i < size; i++) {
count++;
}
return count; }

2.O(n) — 线性阶

线性阶时间复杂度指的是算法的运行时间随着输入规模n增加而以线性级别增长。

线性阶通常出现在单层循环中:
/* 线性阶 */
int linear(int n) {
	int count = 0;
	for (int i = 0; i < n; i++) {
		count++;
	}
	return count;
}

遍历数组和遍历链表等操作的时间复杂度均为 𝑂(𝑛) ,其中 𝑛 为数组或链表的长度:  

/* 线性阶(遍历数组) */
int arrayTraversal(int* nums, int n) {
	int count = 0;
	// 循环次数与数组长度成正比
	for (int i = 0; i < n; i++) {
		count++;
	}
	return count;
}

3.O(n^2) — 平方阶

平方阶时间复杂度指的是算法的运行时间随着输入规模n增加而以平方级别增长。

平方阶通常出现在嵌套循环中,外层循环和内层循环的时间复杂度都为 𝑂(𝑛) ,因此总体的时间复杂度为 𝑂(𝑛 2 )
/* 平方阶 */
int quadratic(int n) {
	int count = 0;
	// 循环次数与数据大小 n 成平方关系
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			count++;
		}
	}
	return count;
}

4.O(2^𝑛) — 指数阶

指数阶时间复杂度指的是算法的运行时间随着输入规模n增加而以指数级别增长。
生物学的“细胞分裂”是指数阶增长的典型例子:初始状态为 1 个细胞,分裂一轮后变为 2 个,分裂两轮后变为 4 个,以此类推,分裂 𝑛 轮后有 2 𝑛 个细胞。

指数时间复杂度通常出现于解决组合问题或递归深度较大的算法:

/* 指数阶(递归实现) */
int expRecur(int n) {
    if (n == 1)
        return 1;
    return expRecur(n - 1) + expRecur(n - 1) + 1;
}

5.O(log 𝑛) — 对数阶 

对数阶时间复杂度指的是算法的运行时间随着输入规模n增加而以对数级别增长。

与指数阶类似,对数阶也常出现于递归函数中。以下代码形成了一棵高度为 log𝑛 的递归树:

/* 对数阶(递归实现) */
int logRecur(int n) {
	if (n <= 1)
		return 0;
	return logRecur(n / 2) + 1;
}
O(log 𝑛) 的底数是多少?
准确来说,“一分为 𝑚 ”对应的时间复杂度是 𝑂( log 𝑚 𝑛) 。而通过对数换底公式,我们可以得到具有不同底数、相等的时间复杂度:
也就是说,底数 𝑚 可以在不影响复杂度的前提下转换。因此我们通常会省略底数 𝑚 ,将对数阶直接记为 𝑂( log 𝑛)

3.总结 

设输入数据大小为 𝑛 ,常见的时间复杂度类型如图 2‑9 所示(按照从低到高的顺序排列)。
 O(1)  < O(log n)  < 𝑂(𝑛) O(n log n) <  O(2^𝑛)  <  O(2^𝑛) O(𝑛!)
常数阶 < 对数阶 < 线性阶 < 线性对数阶 < 平方阶 < 指数阶 < 阶乘阶

 三、空间复杂度

1.概念

空间复杂度(Space Complexity)衡量算法在执行过程中所需的额外内存空间如何随着输入规模的增长而变化。它描述了算法对内存的需求,通常也用大O符号表示。(这个概念与时间复杂度非常类似,只需将“运行时间”替换为“占用内存空间”。

2.常见类型

1.O(1) — 常数阶

常数空间复杂度表示算法所需的额外内存空间不随输入规模变化。例如,交换两个变量的值:
#include <stdio.h>

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

int main() {
    int x = 10, y = 20;
    swap(&x, &y);
    printf("x = %d, y = %d\n", x, y);
    return 0;
}

2.O(n) — 线性阶

线性空间复杂度表示算法所需的额外内存空间与输入规模成正比。例如,创建一个数组:
#include <stdio.h>
#include <stdlib.h>

int *create_array(int size) {
    int *arr = (int *)malloc(size * sizeof(int));
    for (int i = 0; i < size; i++) {
        arr[i] = i;
    }
    return arr;
}

int main() {
    int size = 5;
    int *arr = create_array(size);
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    free(arr);
    return 0;
}

3.O(n^2) — 平方阶

平方空间复杂度表示算法的额外内存使用与输入规模的平方成正比。例如,创建一个二维矩阵:
#include <stdio.h>
#include <stdlib.h>

int **create_matrix(int n) {
    int **matrix = (int **)malloc(n * sizeof(int *));
    for (int i = 0; i < n; i++) {
        matrix[i] = (int *)malloc(n * sizeof(int));
    }
    return matrix;
}

void print_matrix(int **matrix, int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int n = 3;
    int **matrix = create_matrix(n);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            matrix[i][j] = i * n + j + 1;
        }
    }
    print_matrix(matrix, n);
    for (int i = 0; i < n; i++) {
        free(matrix[i]);
    }
    free(matrix);
    return 0;
}

四、总结

理解时间复杂度和空间复杂度是编写高效程序的基础。时间复杂度告诉我们算法的运行时间如何随输入规模变化,而空间复杂度则描述了算法对内存的需求。掌握这些概念可以帮助我们选择和优化算法,提高程序的性能。

希望本文能帮助你更好地理解算法复杂度。如果有任何问题或讨论,请在评论区留言!


http://www.kler.cn/a/306504.html

相关文章:

  • 字节跳动Android面试题汇总及参考答案(80+面试题,持续更新)
  • flutter下拉刷新上拉加载的简单实现方式三
  • MySQL与Oracle对比及区别
  • OCR识别铁路电子客票
  • 机器学习day3-KNN算法、模型调优与选择
  • Mysql 8迁移到达梦DM8遇到的报错
  • Spark_natural_join
  • 828华为云征文 | 华为云Flexusx与Docker技术融合,打造个性化WizNote服务
  • 深入理解中比较两个字符串差异的方法”或“高效比对字符串:diff-match-patch:c++实战指南
  • c++面向对象
  • 栈OJ题——用栈实现队列
  • 嵌入式初学-C语言-数据结构--七
  • 【linux基础】linux中的开发工具(4)--调试器gdb的使用
  • 问题及解决方案汇总
  • 结构体内存对齐
  • 【算法】动态规划—最长公共子序列
  • HTML+CSS - 网页布局之多列布局定位
  • 网络安全应急响应概述
  • 用STM32做一个USB-TTL工具吧
  • JavaScript Promise 异步编程的一些代码分享
  • 远程桌面内网穿透是什么?有什么作用?
  • openssl下载和创建证书
  • 如何在 Visual Studio Code 中反编译具有正确行号的 Java 类?
  • C++:opencv多边形逼近二值图轮廓--cv::approxPolyDP
  • Java集合进阶--双列集合
  • R与机器学习系列|15.可解释的机器学习算法(Interpretable Machine Learning)(下)