#

  1. 链表可以认为是特殊化的树

# 常见术语

  • 根节点:位于树顶部的节点叫作根节点。
  • 叶节点:没有子元素的节点称为叶子节点或外部节点
  • 子树:子树由节点和它的后代构成
  • 深度:节点的深度取决于它的祖先节点的数量
  • 树的高度:取决于所有节点深度的最大值。一

# 二叉树

二叉树中的节点最多只能有两个子节点:

  • 一个是左侧子节点,
  • 一个是右侧子节点

这个定义有助于我们写出更高效地在树中插入、查找和删除节点的算法。二叉树在计算机科学中的应用

二叉搜索树(BST)

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

仔细思考,这样的一棵树,再做查询时效率会大大的提高

# 三、创建二叉树搜索树类

我们要实现一些基本的方法:

  • insert(key):向树中插入一个新的键。
  • search(key):在树中查找一个键。如果节点存在,则返回 true;如果不存在,则返回false。
  • inOrderTraverse():通过中序遍历方式遍历所有节点。
  • preOrderTraverse():通过先序遍历方式遍历所有节点。
  • postOrderTraverse():通过后序遍历方式遍历所有节点。
  • min():返回树中最小的值/键。
  • max():返回树中最大的值/键。
  • remove(key):从树中移除某个键。

# 3.1 二叉搜索树类

在实现具体的方法前,我们要有基础,即一个Tree和Node,之后实现上面的方法~

class BinarySearchTree {
    constructor() {
        this.root = null; // Node 类型的根节点
        this.insert = insert; // 插入节点
        this.search = search; // 搜索节点
        this.inOrderTraverse = inOrderTraverse; // 中序遍历
        this.preOrderTraverse = preOrderTraverse; // 前序遍历
        this.postOrderTraverse = postOrderTraverse; // 后序遍历
        this.min = min; // 最小值
        this.max = max; // 最大值
        this.remove = remove; // 移除指定节点 
    }
}

class Node {
    constructor(key) {
        this.key = key; // 节点值
        this.left = null; // 左侧子节点引用
        this.right = null; // 右侧子节点引用
    }
}

# 3.2 insert - 插入一个节点

利用左子节点小于父节点,右子节点大于父节点的特性,通过递归完成节点的插入

function insert(key) {
    // 要插入的节点
    const node = new Node(key); 
    if (!this.root) {
        this.root = node; // 根节点为空
    } else {
        insertNode(this.root, node); // 推荐子节点
    }
}

function insertNode(root, node) {
    if (root.key > node.key) {
        if (!root.left) {
            root.left = node; // 小的放左边
        } else {
            insertNode(root.left, node); // 继续传递
        }
    } else {
        if (!root.right) {
            root.right = node; // 大的放右边
        } else {
            insertNode(root.right, node); // 继续传递
        }
    }
}

# 3.3 search - 搜索一个节点

  1. 搜索当前节点,当前节点为空证明未找到
  2. 当前节点不满足要求,则像下一层寻找
function search(key) {
    if (!this.root) {

        return false; // 空树
    }

    return this.searchNode(node, key)
}

function searchNode(node, key) {
    if (!node) {
        return false; // 空节点
    }
    if (node.key > key) {
        return searchNode(node.left, key);
    } else if (node.key < key) {
        return searchNode(node.right, key);
    } else {
        return true;
    }
}

# 3.4 inOrderTraverse - 中序遍历

  1. 中序遍历是一种以上行顺序访问BST所有节点的遍历方式,也就是以从最小到最大的顺序
  2. 首先遍历左子树,然后访问根结点,最后遍历右子树。
  3. callback用于遍历时的操作
function inOrderTraverse(callback) {
    inOrderTraverseNode(this.root, callback)
}

function inOrderTraverseNode(node, callback) {
    if (node) {
        // 还有左节点
        inOrderTraverseNode(node.left, callback);
        callback(node.key);
        inOrderTraverseNode(node.right, callback);
    }
}

# 3.5 preOrderTraverse - 前序遍历

前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。

function preOrderTraverse(callback) {
    preOrderTraverseNode(this.root, callback);
}

function preOrderTraverseNode(node, callback) {
    if (node) {
        callback(node.key);
        preOrderTraverseNode(node.left, callback);
        preOrderTraverseNode(node.right, callback);
    }
}

# 3.6 postOrderTraverse - 后序遍历

在二叉树中,先左后右再根,即首先遍历左子树,然后遍历右子树,最后访问根结点。

function postOrderTraverse(callback) {
    postOrderTraverseNode(this.root, callback);
}

function postOrderTraverseNode(node, callback) {
    if (node) {
        preOrderTraverseNode(node.left, callback);
        preOrderTraverseNode(node.right, callback);
        callback(node.key);
    }
}

# 3.7 min

  1. 根据特性,搜索二叉树的最小值一定在树的最左侧
function min() {
    return minNode(this.root);
}

function minNode(node) {
    let cur = node;
    while (cur && cur.left) {
        cur = cur.left;
    }
    return cur;
}

# 3.8 max

  1. 根据特性,搜索二叉树的最大值一定在树的最右侧
function max() {
    return maxNode(this.root);
}

function maxNode(node){
    let cur = node;
    while (cur && cur.right) {
        cur = cur.right;
    }
    return cur;
}

# 3.9 remove

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

function removeNode(node, key, bst) {
    if (node == null) { // {2} 
        return null;
    }
    if(node.key > key){
        node.left = removeNode(node.left, key); // 删除的值在左侧
    }else if(node.key < key){
        node.right = removeNode(node.right, key); // 删除的值在左侧
    }else{
        // 无子节点
        if(!node.left && !node.right){
            node = null;
            return node;
        }
        if(!node.left){
            // 只有右子节点,上移
            node = node.right;
            return node;
        }else if(!node.right){
            // 只有左子节点,上移
            node = node.left;
            return node;
        }
        // 存在左右子节点
        // 左节点,要接在右节点的最左侧
        const min = minNode(node.right); // 找到最小的值
        node.key = min.key;
        node.right = removeNode(node.right, min.key);
        return node;
    }
}

# 3.10 层序遍历

从根节点向下逐层遍历,同一层按照从左到右顺序访问。

function levelOrder(node){
  if(node == null) return;
  let queue = [];
  queue.push(node);
  while(queue.length>0){
    let cur = queue.shift();
    visit(cur);
    if(cur.left!=null){
        queue.push(cur.left)
    }
    if(cur.right != null){
        queue.push(cur.right)
    }
  } 
}

# 四、自平衡二叉树(进阶)

todo

上次更新于: 3/23/2022, 10:11:04 AM