基础算法一条龙,为你提供一种高效的算法学习方法

52 阅读5分钟

引言

扎实的算法能力永远是所有程序员非常重要的一环。无论是参加技术面试还是日常开发,掌握高效的算法学习方法和实战技巧都至关重要。这篇文章将从零开始,结合我自己的学习方法,提供一种高效快捷的学习方法。无论你是初学者还是有一定基础的开发者,都能在这里找到适合自己的学习路径。

y9SdQ8jAibP26z1.jpg

理解算法的核心效率

要想学好算法,不要上来就无脑刷题。首先要理解算法的核心效率,主要深刻理解:时间复杂度空间复杂度

时间复杂度:算法执行所需的时间,具体来说是算法在不同输入规模下的执行次数

常见的时间复杂度包括:

  • O(1) :常数时间复杂度,表示算法的执行时间不随输入规模的变化而变化。
  • O(log n) :对数时间复杂度,常见于分治算法,如二分查找。
  • O(n) :线性时间复杂度,表示算法的执行时间与输入规模成正比。
  • O(n log n) :常见于高效的排序算法,如快速排序和归并排序。
  • O(n^2) :平方时间复杂度,常见于嵌套循环,如冒泡排序。
  • O(n^3) :立方时间复杂度,常见于三重嵌套循环。
  • O(2^n) :指数时间复杂度,常见于递归算法,如某些回溯算法。

空间复杂度:算法在运行过程中占用的额外空间。

常见的空间复杂度包括:

  • O(1) :常数空间复杂度,表示算法的额外空间占用不随输入规模的变化而变化。
  • O(n) :线性空间复杂度,表示算法的额外空间占用与输入规模成正比。

打好算法基础

  • 练习计算复杂度:通过实际例子来练习计算算法的时间和空间复杂度。

下面给出几个例子来说明如何计算时间复杂度和空间复杂度

/////   时间复杂度
// 通过代码执行次数T(n)来计算时间复杂度是一种直观且有效的方法
// 代码的执行次数  T(n)
function traverse(arr) {
    var len = arr.length; //  1
    for (var i = 0; i < len; i++) {   //  (1) + (n + 1) + (n)    分别思考()中每个语句的执行次数
        console.log(arr[i]);  // n
    }
}
// 最后把所有的T()相加,抓住主要矛盾,O(n) 就是执行的趋势。所以系数不重要,可以省略  
// T(n)= 1 + 1 + n + 1 + n + n = 3n+2 = O(n)     
traverse();



function traverse(arr) {
    const outLen = arr.length  //1
    for (let i = 0; i < outLen; i++) { // 1 + n+1 + n
        const inLen = arr[i].length// n
        for (let j = 0; j < inLen; j++) {// n + n*(n+1) + n*n
            console.log(arr[i][j]);// n*n
        }
    }
}
// T(n)=1+1 + n+1 + n+n + n*(n+1) + n*n+n*n=3*n^2+5n+3
// O(n^2) 是主要矛盾,忽略次要,系数不重要
// 随着输入规模的增大,算法对应的执行总次数的一个变化趋势




/////   空间复杂度
// 算法在运行过程中占用的**额外**空间的大小
function traverse(arr) {
    var len = arr.length
    for (var i = 0; i < len; i++) {
        console.log(arr[i]);
    }
}
// 空间复杂度  O(1)
// 输入的arr数组是由函数的调用者提供的,不属于函数本身的额外空间
// 由于 len 和 i 这两个变量占用的空间是固定的,不随输入数组 arr 的大小变化



function init(n) {
    var arr = []
    for (var i = 0; i < n; i++) {
        arr[i] = i
    }
    return arr
}
// 空间复杂度  O(n)
// 数组的占用空间为n

数据结构

数据结构在算法中尤为重要。数据结构是组织和存储数据的方式,它直接影响算法的效率和代码的可读性。选择合适的数据结构可以显著提升程序的性能,简化复杂问题的解决过程。接下来,将简单介绍一下常见的线性数据结构和非线性数据结构。

线性数据结构

  • 数组:使用数组可以快速访问元素,并且有丰富的内置方法,如push, pop, shift, unshift等。
  • 栈和队列:可以利用数组来模拟实现,例如使用pushpop来模拟栈的操作。
  • 链表:如果需要处理不连续的数据或者频繁插入删除操作,可以考虑使用链表。

非线性数据结构

  • :包括二叉树、平衡树等,适合处理层次化数据。
  • :用于表示多对多关系的数据,可以是无向图或有向图。

定义和操作数组

  • 定义数组
    const arr = [1, 2, 3, 4, 5];
    // 或者
    const arr = new Array(7).fill(1);  // 创建一个长度为7,每个元素都是1的数组
    
  • 访问和遍历
    • 直接访问:arr[0] 访问第一个元素。
    • 遍历方法:
      • for循环:
        for (let i = 0; i < arr.length; i++) {
          console.log(arr[i]);
        }
        
      • forEach
        arr.forEach((item, index) => {
          console.log(`Index: ${index}, Value: ${item}`);
        });           // 不会输出新数组
        
      • map
        const newArr = arr.map(item => item * 2);
        console.log(newArr);    // 输出新的数组
        
      • reduce
           const numbers = [1, 2, 3, 4, 5];
           const sum = numbers.reduce((accumulator, currentValue) => {
           return accumulator + currentValue;}, 0);
           console.log(sum); // 15   数组所有元素的和
        

实践建议

不要急于刷题,先理解基础概念,再逐步深入。理解算法思想,不仅仅是记住解法,更重要的是理解背后的算法思想。持续练习,定期回顾已做过的题目,尝试不同的解法。通过这样的学习方法,你将能够更扎实地掌握算法知识,提升解决问题的能力。