重学数据结构与算法-树

246 阅读4分钟

image.png 图:园南小学

如果要系统的学习某项技能的话,我感觉选择相关领域的一本经典书籍来阅读是一个很好的选择。

《数据结构》与《算法》是CS专业最为重要的两门课,特别是对于从事软件开发的同学,也是面试中作为基础知识的最重要的考查点。

这次重新学习数据结构与算法,我主要选了两本书籍作为指南 -《学习Javascript数据结构与算法》和《算法导论》,记得大学的时候,我们的数据结构课本是学校的自编教材,质量实在一般,我也就不太爱看:(。其中《学习Javascript数据结构与算法》主要是用Js代码将主要的数据结构以及操作实现了一遍,整体简洁干练,其中也不乏深度和广度,觉得适合前端人员想学习数据结构的同学。《算法导论》则是很深入讲解算法理论知识的一本经典书籍,里面有大量的数学论证,数学不好的同学可能会看得很吃力,比如说我😫。我是配合b站的书籍作者Charles教授的MIT公开课来学习的,也没有太多的深入。不过如果你能完全搞懂,相信能超越90%程序员,。。

来自知乎的热门话题 - 为什么有人说弄懂了《算法导论》的 90%,就超越了 90%的程序员?

image.png 图:树在数据结构中的“地位”

可以看到树是在属于线性表的链式存储结构。而树中最常见的类型莫非就是二叉树了。二叉树可以简单的定义为 - “二叉树中的节点最多只能有两个子节点,一个是左侧子节点,另一个是右侧子节点”,而二叉树中又有两种常见的类型- Binary Search Tree(二叉搜索树)和 Adelson-Velskii-Landi Tree(自平衡二叉搜索树)。

以下我们来详细的实现下BST,

BST:

BST是二叉树的一种,但是只允许在左侧节点存储比父节点小的值,在右侧节点存储比父节点大的值。

BST的常见操作:

  • 中序遍历(先依次访问左节点,再访问根节点,最后访问右节点,遍历完成后,根节点位于左子树节点与右子树节点中间。)
  • 先序遍历(根节点位于左右子树节点之前,先访问父节点后访问左、右子节点)
  • 后序遍历(根节点位于左右子树节点之后,先访问左、右子节点后访问父节点)
  • 插入
  • 删除某个键
  • 搜索
  • 返回最大/最小键

数据结构

怎么用JS定义Tree的数据结构?

  1. 先定义Tree的每个节点,都包含 - 键(树中习惯叫键,不同于链表的值),左子树、右子树的引用。

class Node {
    constructor(val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}


树节点的实现跟双向链表的实现很像,只是把 this.prev and this.next 换成了this.left and this.right.

  1. 定义一棵二叉树,它应该包含 - 一个根节点,以及上面提到的常见操作方法,这里我们用Class来实现

class BinarySearchTree {
    constructor() {
        this.root = null;
    }
}


  1. 实现插入操作
  • 如果根不存在,则新插入节点直接作为根
  • 否则,从根节点开始递归直到找到应该插入的位置

  // 向树插入一个新键
  insert(key, currentNode = this.root) {
    if (!this.root) {
      this.root = new Node(key);
    } else {
      this.insertNode(key, this.root);
    }
  }

    // 找到新节点应该插入的位置
  insertNode(key, node) {
    if (this.compareFn(key, node.key) === CompareResult.LESS_THAN) {
      if (!node.left) {
        node.left = new Node(key);
        return;
      } else {
        this.insertNode(key, node.left);
      }
    } else {
      if (!node.right) {
        node.right = new Node(key);
        return;
      } else {
        this.insertNode(key, node.right);
      }
    }
  }


  1. 中序遍历

  // 中序遍历 - 根节点位于左子树节点与右子树节点中间,先访问左节点,再访问根节点,最后访问右节点
  inorderTreeWalk() {
    this.inorderNode(this.root);
  }
  inorderNode(node) {
    if (node !== null) {
      this.inorderNode(node.left);
      console.log(node.key);
      this.inorderNode(node.right);
    }
    return undefined;
  }

  1. 先序遍历

// 先序遍历 - 根节点位于左右子树节点之前,先访问父节点后访问左、右子节点
  preorderTreeWalk() {
    this.preorderNode(this.root);
  }

  preorderNode(node) {
    if (node !== null) {
      console.log(node.key);
      this.preorderNode(node.left);
      this.preorderNode(node.right);
    }
    return undefined;
  }

  1. 后序遍历

  // 后序遍历 - 根节点位于左右子树节点之后,先访问左、右子节点后访问父节点
  postorderTreeWalk() {
    this.postorderNode(this.root);
  }

  postorderNode(node) {
    if (node !== null) {
      this.postorderNode(node.left);
      this.postorderNode(node.right);
      console.log(node.key);
    }
    return;
  }

  1. 删除某个节点
  • 先递归找到要删除的节点
  • 若节点没有左右子树(叶节点),则可以直接删除
  • 若节点仅有一棵子树,则删除当前节点并将子树上移
  • 若节点有左右子树,则找到右子树的最小值来替代要当前节点(或左子树的最大值),然后再删除右子树中的最小值

  // 移除一个节点
  remove(key) {
    return this.removeNode(key, this.root);
  }

  removeNode(key, current) {
    if (this.compareFn(key, current.key) === CompareResult.LESS_THAN) {
      current.left = this.removeNode(key, current.left);
      return current;
    } else if (
      this.compareFn(key, current.key) === CompareResult.GREATER_THAN
    ) {
      current.right = this.removeNode(key, current.right);
      return current;
    } else {
      // 没有子树,则直接删除
      if (!current.left && !current.right) {
        current = null;
        return current;
      }
     // 只有一棵子树,子树上移
      if (!current.left) {
        current = current.right;
        return current;
      } else if (!current.right) {
        current = current.left;
        return current;
      }
     // 左右子树都有的情况
      const minNode = this.minNode(current.right);
      current.key = minNode.key;
      current.right = this.removeNode(minNode.key, current.right);
      return current;
    }
  }

... 其它操作方法的实现,可以参考我的codepen

二叉树在计算机科学中的应用

  • 利用中序遍历对一组数进行排序
  • 待定。。。

其它知识点

  • 树的顶部节点 - 根节点
  • 没有子元素的节点 - 叶节点
  • 树的最小键存在于左子树的最左端点
  • 树的最大键存在于右子树的最右端点

博客原文