算法基础概念

154 阅读3分钟

如何计算算法的时间复杂度和空间复杂度

时间复杂度计算: 时间复杂度衡量算法运行所需时间随输入规模变化的增长情况,通常用大O符号表示。

  • 步骤:
    • 分析基本操作:找出算法中最核心操作(如循环、递归等),并统计其执行次数。
    • 忽略忽略常数项:只关注输入规模n对执行次数的影响,忽略常数因子和低阶项。
    • 找出增长趋势:确定算法中最耗时的部分,并用大O符号表示增长趋势。
  • 常见复杂度级别:
    • O(1):常数时间,算法运行时间不随输入规模变化。
    • O(logn):对数时间,常见二分查找。
    • O(n): 线性时间,常见单层循环。
    • O(nlogn):常见归并排序、快速排序的平均情况
    • O(n²):平方时间,常见嵌套循环
    • O(2ⁿ):指数时间,常见递归问题(斐波那契数列的朴素递归)
    • O(n!):阶乘时间,常见全排列问题的暴力解法。
function sumArray(arr) {
    let sum = 0; // O(1)
    for (let i=0; i < arr.length; i++) { // O(n)
        sum += arr[i]; // O(1)
    }
    return sum; // O(1)
}

总时间复杂度:O(n)


空间复杂度的计算: 空间复杂度衡量算法运行时占用的额外内存空间(不包括输入数据本身)。

  • 步骤:
    • 分析变量存储:统计算法中定义的变量、数组、递归栈等占用的空间
    • 忽略常数项:只关注输入规模n对空间的影响
    • 找出增长趋势:确定随着输入规模增长,额外空间需求的变化趋势
  • 常见复杂度级别:
    • O(1):常数空间,算法不依赖输入规模
    • O(n):线性空间,常见需要额外存储输入的算法(动态规划表)
    • O(n²):平方空间,常见二维矩阵操作
function createArray(n) {
    let arr = new Array(n); // O(n)
    return arr;
}

空间复杂度:O(n)


什么是栈?JavaScript中有哪些栈的具体应用?

  • 栈(Stack)是一种 先进后出(LIFO,Last In First Out) 的数据结构
  • 栈的基本操作:
    • push:将元素压入栈顶
    • pop:从栈顶弹出元素
    • peek/top:查看栈顶元素
    • isEmpty:判断是否为空
  • 特点:
    • 只能在一端(栈顶)进行插入和删除操作
    • 常用于递归调用、括号匹配、表达式求值等场景

JavaScript中栈的具体应用:

  • 1. 函数调用栈(Call Stack):
    • JavaScript的执行环境是单线程,函数调用会按照栈的形式管理
    • 当一个函数被调用时,它会被压入调用栈;函数执行完毕后会从栈中弹出
function a() {
    console.log('a');
    b();
}
function b() {
    console.log('b');
    c();
}
function c() {
    console.log('c');
}
a(); // 调用

调用栈变化:

1. 调用 a() -> [a]
2. 调用 b() -> [a, b]
3. 调用 c() -> [a, b, c]
4. c() 执行完弹出 -> [a, b]
5. b() 执行完弹出 -> [a]
6. a() 执行完弹出 -> []
  • 2. 括号匹配问题:
    • 栈常用于检查括号是否匹配
function isValid(s) {
    const stack = [];
    const map = { `)`: `(`, `}`: `{`, ']': `[` };
    for (let char of s) {
        if (char in map) {
            if (stack.pop() !== map[char]) return false;
        } else {
            stack.push(char);
        }
    }
    return stack.length === 0;
}
console.log(isValid("({[]})")); // true
console.log(isValid("({[})")); // false
  • 3. 浏览器历史记录:
    • 浏览器的前进、后退功能可用2个栈实现
    • 一个栈保存前进页面,一个栈保存后退页面
  • 4. 递归
    • JavaScript的递归调用本质上是依赖调用栈
function factorial(n) {
    if (n === 1) return 1;
    return n * factorial(n - 1);
}
console.log(factorial(5)); // 120

每次递归调用会将当前函数压入调用栈,直到递归结束再逐个弹出。

  • 5. 表达书求值:
    • 栈可用中缀表达式转为后缀表达式(逆波兰表达式)以及后缀表达式的求值。
  • 6. 深度优先搜索(DFS):
    • 使用栈模拟递归过程,遍历图或树结构。