重学算法-算法本质探讨

43 阅读3分钟

重新梳理算法,看labuladong 相关文章,有感,记录

算法

  1. 提到算法,其实主要可以拆解为两个部分
  2. 数据结构本身,用于解决对于数据的增删改查
  3. 而所谓的算法,就是更高效,自动化穷举, 比如 .1 递归,动态规划,回溯.
  4. 穷举的关键点在于无遗漏和无冗余
  5. 那么常用的数据结构,具体划分其实只有两种 ;1. 数组,链表
  6. 队列和栈是用数组或者链表模拟实现的
  7. 常用的树结构,可以通过指针指向,模拟左右子树.

那么从根源上来讲

数组: 从访问的角度,o1 内存连续,可通过索引来定位,,插入,删除的角度,on,因为需要进行后续内容移动

链表: 访问的角度为on 删除的角度为o1 操作,只需要进行指针修改,并且从单个节点角度看,相比于单个数组元素,消耗空间更大,因为还要进行存储前后指针.

那么从数据结构操作的角度来看

  1. 就是对于数据增删改查--> ?
  2. 从遍历和访问的角度来看,就是 线性和非线性,
  3. 循环就是线性,递归一类就是非线性

那既然算法的目标就是穷举

那么到底怎么穷举呢

无遗漏,无冗余

无遗漏,就是对于边界情况要规避

无冗余,就是要重复利用已知条件进行剪枝,比如回溯算法

回溯强调遍历

动态规划强调 分解,

动态规划系列问题的解题过程,无非就是先写出暴力穷举解法(状态转移方程),加个备忘录就成自顶向下的递归解法了,再改一改就成自底向上的递推迭代解法

穷举的核心是数学归纳法,明确函数的定义,分解问题,然后利用这个定义递归求解子问题

比如入门刷的数组,链表算法考察的就是如何高效的穷举

通过双指针,减少遍历时间复杂度

对于搜索: 对于有序数组,通过二分算法来进行搜索从on--ologn

对于滑动窗口: 就是通过快慢指针来解决,避免双重for 循环,在On 下进行遍历

对于二叉树

1 两种思维 :遍历的思维, 分解的思维

对于树的深度, 可以遍历获取,对于树中节点值的加总,可以遍历一遍求解

var maxDepth = function(root) {
    // 记录最大深度
    let res = 0;
    // 记录当前遍历节点的深度
    let depth = 0;

    // 主函数
    function traverse(root) {
        if (root === null) {
            // 到达叶子节点
            res = Math.max(res, depth);
            return;
        }
        // 前序遍历位置
        depth++;
        traverse(root.left);
        traverse(root.right);
        // 后序遍历位置
        depth--;
    }

    traverse(root);
    return res;
};

那么对于回溯 ,其实核心代码就是遍历一颗多叉树

   if (track.length === nums.length) {
            res.push(track.slice());
            return;
        }

        for (let i = 0; i < nums.length; i++) {
            // 排除不合法的选择
            if (used[i]) {
                // nums[i] 已经在 track 中,跳过
                continue;
            }
            // 做选择
            track.push(nums[i]);
            used[i] = true;

            // 进入递归树的下一层
            backtrack(nums);

            // 取消选择
            track.pop();
            used[i] = false;
        }

以一个分解的思路来看二叉树前序遍历

// 定义:输入一棵二叉树的根节点,返回这棵树的前序遍历结果
var preorder = function(root) {
    var res = [];
    if (root === null) {
        return res;
    }
    // 前序遍历的结果,root.val 在第一个
    res.push(root.val);
    // 后面接着左子树的前序遍历结果
    res = res.concat(preorder(root.left));
    // 最后接着右子树的前序遍历结果
    res = res.concat(preorder(root.right));
    return res;
};