一文看懂时间复杂度和空间复杂度【已附代码详解】

引言

在计算机科学中,数据结构和算法是构建高效软件系统的基础。数据结构提供了组织、管理和存储数据的方式,而算法则是解决问题的一系列有序步骤。了解和掌握它们对于任何软件开发者来说都是至关重要的。


在这里插入图片描述

正文开始如果觉得文章对您有帮助,请帮我三连+订阅,谢谢💖💖💖


数据结构基础

定义

数据结构是计算机科学中存储、组织数据的方式,它使得我们可以高效地访问和修改数据。数据结构通常包括数据的逻辑关系,这些关系定义了数据元素之间的相互作用。

重要性

  • 效率:选择合适的数据结构可以显著提高程序的执行效率。
  • 可读性:良好的数据结构设计可以提高代码的可读性和可维护性。
  • 功能:不同的数据结构支持不同的操作,选择正确的数据结构可以简化实现复杂功能的过程。

常见数据结构

  • 数组:连续的内存空间,支持快速随机访问。
  • 链表:由一系列节点组成,每个节点包含数据部分和指向下一个节点的指针。
  • :后进先出(LIFO)的数据结构。
  • 堆(Heap):一种特殊的树形数据结构,满足堆性质:即父节点的值总是大于或小于它的子节点(最大堆或最小堆)。
  • 队列:先进先出(FIFO)的数据结构。
  • 哈希表:通过键值对存储数据,支持快速查找。
  • 图(Graph):由顶点(或节点)和边组成,可以表示复杂的关系,如网络、路径等。
  • 并查集(Disjoint Set or Union-Find): 用于处理一些不交集(Disjoint Sets)的合并及查询问题,常用于解决网络连通性问题。
  • 树(Tree):由节点组成的层次结构,每个节点有零个或多个子节点,常用于表示具有层次关系的数据。
    • 二叉树(Binary Tree): 每个节点最多有两个子节点(左子节点和右子节点)的树结构。
    • 二叉搜索树(Binary Search Tree, BST): 一种特殊的二叉树,其中每个节点的左子树只包含小于当前节点的键,右子树只包含大于当前节点的键。
    • 平衡树(Balanced Tree): 一种自平衡的二叉搜索树,如AVL树和红黑树,它们通过在插入和删除操作后调整树结构来保持树的平衡。
    • 字典树(Trie): 用于检索字符串数据集中的键的数据结构,允许在时间复杂度为O(n)的情况下快速插入和查找字符串。

算法基础

定义

算法是解决特定问题的一系列计算步骤。一个好的算法应该具备正确性、可终止性、有穷性,并在资源使用上尽可能高效。

时间复杂度

时间复杂度是衡量算法执行时间的指标,通常用大O符号表示。它描述了算法执行时间随输入规模增长的变化趋势。

  • O(1)常数时间复杂度,与输入规模无关。
  • O(log n)对数时间复杂度,常用于二分搜索。
  • O(n)线性时间复杂度,操作数与输入规模成正比。
  • O(n log n)对数线性时间复杂度,常用于快速排序等。
  • O(n^2)平方时间复杂度,常见于简单排序算法。
  • O(2^n):指数时间复杂度 算法运行时间随输入规模呈指数增长,通常效率较低。
  • O(n!):阶乘时间复杂度 算法运行时间与输入规模的阶乘成正比,效率非常低,常见于某些排列问题。
    在这里插入图片描述

空间复杂度

空间复杂度是衡量算法执行过程中所需存储空间的指标。它同样用大O符号表示,描述了算法占用空间随输入规模增长的变化趋势。

时间复杂度示例代码

以下代码将用 Javascript 语法演示

O(1) - 常数时间复杂度

基础代码,什么都没有

// 常数时间操作
function constantTimeOperation() {
  let a = 1,
    b = 2;
  let c = a + b;
  console.log("输出结果:", c);
  return c;
}

O(log n) - 对数时间复杂度

binarySearch函数要求参数数组是 已排序的,

function binarySearch(arr, target) {
  let low = 0;
  let high = arr.length - 1;
  while (low <= high) {
    let mid = Math.floor((low + high) / 2);
    if (arr[mid] === target) {
      return mid;
    } else if (arr[mid] < target) {
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }
  return -1;
}
let arr = [1,3,5,7,9,11,13,15] 
let index = binarySearch(arr,5)
console.log(`index: ${index}`)

O(n) - 线性时间复杂度

常见的for循环

function linearTimeOperation(arr) {
  let sum = 0;
  for (let i = 0; i < arr.length; i++) {
    sum += arr[i]; // 线性时间操作
  }
  return sum;
}

let arr = [1,3,5,7,9,11,13,15] 
let sum = linearTimeOperation(arr)
console.log(`sum: ${sum}`)

O(n log n) - 对数线性时间复杂度

典型快速排序算法

function quickSort(arr) {
  if (arr.length < 2) {
    return arr;
  }
  let pivot = arr[0];
  let left = [];
  let right = [];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return [...quickSort(left), pivot, ...quickSort(right)];
}


O(n^2) - 平方时间复杂度

常见的冒泡算法

function bubbleSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
}

let arr = [1,8,3,2,12,6,11,4,15] 
let res = bubbleSort(arr)
console.log(`res: ${res}`)

O(2^n) - 指数时间复杂度

简单的递归函数

function recursiveExponential(n) {
  if (n === 0) {
    return 1;
  }
  return recursiveExponential(n - 1) + recursiveExponential(n - 1);
}

O(n!) - 阶乘时间复杂度

常见结成算法

function factorial(n) {
  if (n === 0 || n === 1) {
    return 1;
  }
  return n * factorial(n - 1);
}

空间复杂度示例代码解释:

空间复杂度是指算法在执行过程中所需的存储空间,这包括输入数据所占的空间以及算法执行过程中临时占用的空间。

O(1) - 常数空间复杂度

常数空间复杂度意味着算法使用的空间不随输入规模的变化而变化。

function constantSpace() {
    let result = 42; // 使用的额外空间是常数
    return result;
}

O(log n) - 对数空间复杂度

对数空间复杂度通常与树结构的深度有关,例如二叉搜索树。

function logSpace(arr, low, high, target) {
    if (high >= low) {
        let mid = Math.floor((low + high) / 2);
        if (arr[mid] === target) {
            return [mid]; // 找到一个元素
        } else if (arr[mid] > target) {
            return logSpace(arr, low, mid - 1, target); // 递归搜索左半部分
        } else {
            return logSpace(arr, mid + 1, high, target); // 递归搜索右半部分
        }
    }
    return []; // 未找到
}

O(n) - 线性空间复杂度

线性空间复杂度意味着算法使用的空间与输入规模成正比。

function linearSpace(arr) {
    let newArr = new Array(arr.length); // 创建一个与输入数组等长的数组
    for (let i = 0; i < arr.length; i++) {
        newArr[i] = arr[i] * 2; // 复制并修改原数组的元素
    }
    return newArr;
}

O(n^2) - 平方空间复杂度

平方空间复杂度通常出现在使用二维数组存储数据时。

function squareSpace(rows, cols) {
    let matrix = new Array(rows); // 创建一个行数组
    for (let i = 0; i < rows; i++) {
        matrix[i] = new Array(cols); // 每行创建一个列数组
        for (let j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j; // 初始化矩阵元素
        }
    }
    return matrix;
}

O(2^n) - 指数空间复杂度

指数空间复杂度通常出现在递归算法中,尤其是那些有大量重复计算的算法。

function exponentialSpace(n) {
    if (n === 0) {
        return [];
    }
    let subProblem1 = exponentialSpace(n - 1);
    let subProblem2 = exponentialSpace(n - 1);
    let combined = subProblem1.concat(subProblem2); // 合并两个子问题的解
    return combined;
}

O(n!) - 阶乘空间复杂度

阶乘空间复杂度非常罕见,通常与某些特定的递归算法有关。

function factorialSpace(n) {
    if (n === 0 || n === 1) {
        return [1]; // 只有一个元素的数组
    }
    let subFactorial = factorialSpace(n - 1);
    let newLevel = new Array(n); // 为当前层创建一个数组
    for (let i = 0; i < n; i++) {
        newLevel[i] = subFactorial.map(el => el * (n - i)); // 复制并修改下一层的元素
    }
    return subFactorial.concat(newLevel); // 合并前一层和当前层
}

总结

数据结构和算法是软件开发中的基石。掌握它们不仅能够帮助我们写出更高效的代码,还能提高我们解决问题的能力。通过实际的代码示例,我们可以更深入地理解每种数据结构和算法的工作原理和使用场景。随着技术的不断发展,新的数据结构和算法也在不断涌现,持续学习是每个程序员的必修课。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子羽bro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值