js数据结构与算法笔记(八)—— 树

·  阅读 81

是一种非顺序的数据结构,适合存储需要快速查找的数据。

文档包含以下内容:

  • 树的相关概念
  • 二叉树
  • 树的遍历
  • 树的节点添加、移除
  • AVL树

1. 树的相关概念

一个数结构包含一系列父子关系的节点。每个节点都有一个父节点一级0个或多个子节点。

树.png

  • 根节点:位于树顶部的节点;
  • 内部节点:至少有一个子节点的节点;
  • 外部节点叶节点:没有子节点的节点;
  • 子树:有节点和它的后代构成;
  • 节点的深度:取决于它的祖先节点的数量;
  • 树的高度:取决于所有节点深度的最大值。上图中输的高度为3。

2. 二叉树

二叉树:只有2个子节点的树。

二叉搜索树(BST) 是二叉树的一种,但是只能在左侧子节点存储(比父节点)小的值,在右侧子节点存储(比父节点)大的值。

2.1 创建BST类

  • Node 类表示二叉树中的每个节点
class Node {
    constructor(key){
        this.key = key;// 节点值
        this.left = null;// 左侧子节点引用
        this.right = null; // 右侧子节点引用
    }
}
复制代码
  • 二叉搜索树组织方式

image.png 通过指针你表示节点之间的关系; 键是树相关术语中对节点的称呼。

  • BST类基本结构
const Compare = {
  LESS_THAN: -1,
  BIGGER_THAN: 1,
  EQUALS: 0,
};

function defaultCompare(a, b) {
  if (a === b) {
    return Compare.EQUALS;
  }
  return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}

class BinarySearchTree {
  constructor(compareFn = defaultCompare) {
    this.compareFn = compareFn; // 比较节点值
    this.root = null; // Node类型的根节点
  }
}
复制代码

2.2 向二叉搜索树中插入一个键

/**
   * 向二叉搜索树中插入一个键
   * @param {*} key 插入的值
   */
  insert(key) {
    if (this.root == null) {
      // 树为空时;
      this.root = new Node(key);
    } else {
      // 树不为空时
      this.insertNode(this.root, key);
    }
  }

  insertNode(node, key) {
    //   如果新节点的键小于当前节点的键,检查当前节点的左侧子节点
    if (this.compareFn(key, node?.key) === Compare.LESS_THAN) {
      if (node.left == null) {
        node.left = new Node(key);
      } else {
        //   如果有左侧子节点
        this.insertNode(node.left, key);
      }
    } else {
      // 如果节点的键>当前节点的键,同时当前节点没有右侧子节点,那么插入新的节点。
      if (node.right == null) {
        node.right = new Node(key);
      } else {
        //   如果有右侧子节点
        this.insertNode(node.right, key);
      }
    }
  }
复制代码

执行以下代码,生成二叉树;

let bst = new BinarySearchTree();
bst.insert(11);
bst.insert(15);
bst.insert(7);
bst.insert(5);
bst.insert(9);
bst.insert(3);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
复制代码

如下图:

image.png 再次插入6的结果:

image.png

3. 树的遍历

遍历树是指访问数的每个节点并对他们进行某种操作的过程。

遍历树的三种方式:中序、先序、后序。

3.1 中序遍历

定义:从最小到最大的顺序访问所有节点。

应用:对树进行排序操作。

/**
   * 中序遍历
   * @param {Function} callback 对遍历到的每个节点进行操作
   */
  inOrderTraverse(callback) {
    inOrderTraverseNode(this.root, callback);
    function inOrderTraverseNode(node, callback) {
      if (node != null) {
        // 停止递归的条件

        // 访问左侧子节点
        inOrderTraverseNode(node.left, callback);
        callback(node.key);
        // 访问右侧子节点
        inOrderTraverseNode(node.right, callback);
      }
    }
  }
复制代码

访问路径:

bst.inOrderTraverse((key) => {
  console.log(key);
});
复制代码

image.png

3.2 先序遍历

定义:先于后代节点的顺序访问每个节点。

应用:打印一个结构化文档。

实现方法:

/**
   * 先序遍历
   */
  preOrderTraverse(callback) {
    preOrderTraverseNode(this.root, callback);
    function preOrderTraverseNode(node, callback) {
      if (node != null) {
        callback(node.key); // 先访问节点本身
        preOrderTraverseNode(node.left, callback);
        preOrderTraverseNode(node.right, callback);
      }
    }
  }
复制代码

访问:

bst.preOrderTraverse((key) => {
  console.log(key);
});
复制代码

路径:

image.png

先序遍历和中序遍历区别:先序遍历会先访问节点本身,在访问它的左侧子节点,最后访问右测子节点。

3.3 后序遍历

定义:先访问节点的后代节点,在访问节点本身。

应用:计算目录及其子目录中所有文件作战空间的大小。

实现方法:

/**
   * 后序遍历
   */
  postOrderTraverse(callback) {
    postOrderTraverseNode(this.root, callback);
    function postOrderTraverseNode(node, callback) {
      if (node != null) {
        postOrderTraverseNode(node.left, callback);
        postOrderTraverseNode(node.right, callback);
        callback(node.key); // 最后访问节点本身
      }
    }
  }
复制代码

访问:

bst.postOrderTraverse((key) => {
  console.log(key);
});
复制代码

访问路径图:

image.png

4. 搜索树中的值

4.1 最大值和最小值

如下图中的树:

image.png 有上图中可发现:最后一层最左侧的节点是树中的最小键。最右侧的节点的树中的最大键。

树的最小键 实现方法:

/**
   * 树的最小键(值)
   */
  min() {
    return this.minNode(this.root);
  }

  minNode(node) {
      let current =node;
      while (current!=null&&current.left!=null) {
          current = current.left;
      }
      return current;
  }
复制代码

执行方法:

console.log(bst.min()); // Node { key: 3, left: undefined, right: undefined }
复制代码

树的最大键

实现方法:

max() {
    return this.maxNode(this.root);
  }

  maxNode(node) {
      let current =node;
      while (current!=null&&current.right!=null) {
          current = current.right;
      }
      return current;
  }
复制代码

执行方法:

console.log(bst.max()); // Node { key: 25, left: undefined, right: undefined }
复制代码

结论:寻找最小值,总是沿着树的左边;寻找最大值,总是沿着树的右边。

4.2 搜索特定的值

  • 在bst中的搜索 实现方法:
  search(key) {
    return this.searchNode(this.root, key);
  }

  searchNode(node, key) {
    if (node == null) {
      return false;
    }

    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
      return this.searchNode(node.left, key);
    } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
      return this.searchNode(node.right, key);
    } else {
      return true;
    }
  }
复制代码

4.3 移除一个节点

实现方法:

remove(key){
    this.root = this.removeNode(this.root, key);
  }

  removeNode(node, key) {
    // root为空
    if (node == null) {
      return null;
    }

    // 移除的key小于node.key
    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
      // 沿着树的左边继续向下找
      node.left = this.removeNode(node.left, key);
      return node;
    }else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
      // 移除的key > node.key

      node.right = this.removeNode(node.right, key);
      return node;
    }else {
      // key = node.key

      // (1)移除一个叶节点
      if (node.left== null&& node.right== null) {
        node =null;
        return node;
      }

      // (2) 移除有一个左侧或右侧子节点的节点
      if (node.left== null) {
        node = node.right;
        return node;
      }else if(node.right== null) {
        node = node.left;
        return node;
      }

      // (3) 移除有两个子节点的节点
      const aux = this.minNode(node.right);
      node.key = aux.key;
      node.right = this.removeNode(node.right, key);
      return node;
    }
  }
复制代码
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改