数据结构--树

645 阅读5分钟

什么是树

树(Tree): 它是由 n(n>=1)个有限结点组成一个具有层次关系的集合。

真实的树:

树的特点

  • 树一般都有一个根,连接着根的是树干;
  • 树干会发生分叉,形成许多树枝,树枝会继续分化成更小的树枝;
  • 树枝的最后是叶子;

树结构对比于数组/链表/哈希表有哪些优势呢?

数组:

  • 优点:可以通过下标值访问,效率高;
  • 缺点:查找数据时需要先对数据进行排序,生成有序数组,才能提高查找效率;并且在插入和删除元素时,需要大量的位移操作;

链表

  • 优点:数据的插入和删除操作效率都很高;
  • 缺点:查找效率低,需要从头开始依次查找,直到找到目标数据为止;当需要在链表中间位置插入或删除数据时,插入或删除的效率都不高。

哈希表

  • 优点:哈希表的插入/查询/删除效率都非常高;
  • 缺点:空间利用率不高,底层使用的数组中很多单元没有被利用;并且哈希表中的元素是无序的,不能按照固定顺序遍历哈希表中的元素;而且不能快速找出哈希表中最大值或最小值这些特殊值。

树结构

  • 优点:树结构综合了上述三种结构的优点,同时也弥补了它们存在的缺点(虽然效率不一定都比它们高),比如树结构中数据都是有序的,查找效率高;空间利用率高;并且可以快速获取最大值和最小值等。

什么是二叉树

二叉树:如果树中的每一个节点最多只能由两个子节点,这样的树就称为二叉树

二叉搜索树也属于二叉树,也可以为空。

如果不为空必须满足以下三个条件:

  • 条件 1:非空左子树的所有键值小于其根节点的键值。
  • 条件 2:非空右子树的所有键值大于其根节点的键值。
  • 条件 3:左、右子树本身也都是二叉搜索树。

如下图:

二叉搜索树的常见操作

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

二叉搜索树的封装

二叉搜索树有四个最基本的属性:指向节点的根(root),节点中的键(key)、左指针(right)、右指针(right)。

  • 节点类的封装
class TreeNode {
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}
  • 二叉树的封装
class BST {
  Root = null;
}

  • insert(key):方法封装
// 1. insert(key):向二叉搜索树中插入一个新的键;

  insert(key) {
    // 1. 创建树节点
    const Node = new TreeNode(key);

    // 2. 判断是否存在根节点
    if (this.Root === null) {
      this.Root = Node;
    } else {
      this.insertNode(this.Root, Node);
    }
  }
  insertNode(oldNode, newNode) {
    // 先查找左边
    if (oldNode.value > newNode.value) {
      // 1. 判断子节点是否存在左节点
      if (oldNode.left === null) {
        oldNode.left = newNode;
      } else {
        this.insertNode(oldNode.left, newNode);
      }
    } else {
      // 查找右边
      // 1. 判断子节点是否存在右节点
      if (oldNode.right === null) {
        oldNode.right = newNode;
      } else {
        this.insertNode(oldNode.right, newNode);
      }
    }
  }
  • search(key):方法封装
// 2 search(key):在二叉搜索树中查找一个键,如果节点存在,则返回true;如果不存在,则返回false;
  searchKey(key) {
    let node = this.Root;
    while (node !== null) {
      if (key < node.key) {
        node = node.left;
      } else if (key > node.key) {
        node = node.right;
      } else {
        return node;
      }
    }
    return false;
  }
  • preorderTraversal:方法封装
 // 3. preorderTraversal:通过先序遍历方式遍历所有节点;
  preorderTraversal() {
    // 先序遍历(根左右 DLR)
    const dataArr = [];
    this.preorderTraversalNode(this.Root, dataArr);
    return dataArr;
  }
  preorderTraversalNode(oldNode, arr) {
    if (oldNode === null) return;
    const { key, value } = oldNode;
    arr.push({ key, value });
    this.preorderTraversalNode(oldNode.left, arr);
    this.preorderTraversalNode(oldNode.right, arr);
  }
  • inorderTraversal:方法封装
// 3. 中序遍历
  // 中序遍历(左根右 LDR)
  inorderTraversal() {
    const dataArr = [];
    this.inorderTraversalNode(this.Root, dataArr);
    return dataArr;
  }
  inorderTraversalNode(oldNode, arr) {
    if (oldNode === null) return;
    this.preorderTraversalNode(oldNode.left, arr);
    const { key, value } = oldNode;
    arr.push({ key, value });
    this.preorderTraversalNode(oldNode.right, arr);
  }
  • postorderTraversal:方法封装
 // 4. 后序遍历(左右根 LRD)
  postorderTraversal() {
    const dataArr = [];
    this.postorderTraversalNode(this.Root, dataArr);
    return dataArr;
  }

  postorderTraversalNode(oldNode, arr) {
    if (oldNode === null) return;
    this.postorderTraversalNode(oldNode.left, arr);
    this.postorderTraversalNode(oldNode.right, arr);
    const { key, value } = oldNode;
    arr.push({ key, value });
  }
  • min:方法封装
 //6. 寻找最小值
  min() {
    if (!this.Root) return false;
    let node = this.Root;
    while (node.left !== null) {
      node = node.left;
    }
    return node;
  }
  • max: 方法封装
  //5. 寻找最大值

  max() {
    if (!this.Root) return false;
    let node = this.Root;
    while (node.right !== null) {
      node = node.right;
    }
    return node;
  }

  • remove(key):方法封装
// 删除节点
  remove(key) {
    let currentNode = this.Root;
    let parentNode = null;
    // 默认这个key是左节点
    let isLeftChild = true;

    // 查找节点
    while (currentNode.key !== key) {
      parentNode = currentNode;

      // 小于往左查找
      if (key < currentNode.key) {
        isLeftChild = true;
        currentNode = currentNode.left;
      } else {
        isLeftChild = false;
        currentNode = currentNode.right;
      }
      // 如果到最后都没找到key,返回false
      if (currentNode === null) {
        return false;
      }
    }
    // 删除叶子节点
    if (currentNode.left === null && currentNode.right === null) {
      if (currentNode === this.Root) {
        this.Root = null;
      } else if (isLeftChild) {
        parentNode.left = null;
      } else {
        parentNode.right = null;
      }
    }

    // 删除只有一个子节点的节点
    else if (currentNode.right === null) {
      if (currentNode === this.Root) {
        this.Root = currentNode.left;
      } else if (isLeftChild) {
        parentNode.left = currentNode.left;
      } else {
        parentNode.right = currentNode.left;
      }
    } else if (currentNode.left === null) {
      if (currentNode === this.Root) {
        this.Root = currentNode.right;
      } else if (isLeftChild) {
        parentNode.left = currentNode.right;
      } else {
        parentNode.right = currentNode.left;
      }
    }
    // 删除有两个子节点的节点
    else {
      // 找到后续节点
      let successor = this.getSuccessor(currentNode);

      // p、判断是否为根节点
      if (currentNode === this.Root) {
        this.Root = successor;
      } else if (isLeftChild) {
        parentNode.left = successor;
      } else {
        parentNode.right = successor;
      }
      // 将后续节点改为被删除的左节点
      successor.left = currentNode.left;
    }
  }
  // 获取后续节点,即从要删除的节点的右边开始查找最小的值
  getSuccessor(delNode) {
    // 定义变量,保存要找到的后续
    let successor = delNode;
    let current = delNode.right;
    let successorParent = delNode;

    // 循环查找 current 的右子树节点
    while (current !== null) {
      successorParent = successor;
      successor = current;
      current = current.left;
    }

    // 判断寻找到的后续节点是否直接就是要删除节点的 right
    if (successor !== delNode.right) {
      successorParent.left = successor.right;
      successor.right = delNode.right;
    }
    return successor;
  }

二叉搜索树的完整代码

class TreeNode {
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}
class BST {
  Root = null;

  insert(key) {
    // 1. 创建树节点
    const Node = new TreeNode(key);

    // 2. 判断是否存在根节点
    if (this.Root === null) {
      this.Root = Node;
    } else {
      this.insertNode(this.Root, Node);
    }
  }
  insertNode(oldNode, newNode) {
    // 先查找左边
    if (oldNode.value > newNode.value) {
      // 1. 判断子节点是否存在左节点
      if (oldNode.left === null) {
        oldNode.left = newNode;
      } else {
        this.insertNode(oldNode.left, newNode);
      }
    } else {
      // 查找右边
      // 1. 判断子节点是否存在右节点
      if (oldNode.right === null) {
        oldNode.right = newNode;
      } else {
        this.insertNode(oldNode.right, newNode);
      }
    }
  }


  searchKey(key) {
    let node = this.Root;
    while (node !== null) {
      if (key < node.key) {
        node = node.left;
      } else if (key > node.key) {
        node = node.right;
      } else {
        return node;
      }
    }
    return false;
  }

  preorderTraversal() {
    // 先序遍历(根左右 DLR)
    const dataArr = [];
    this.preorderTraversalNode(this.Root, dataArr);
    return dataArr;
  }
  preorderTraversalNode(oldNode, arr) {
    if (oldNode === null) return;
    const { key, value } = oldNode;
    arr.push({ key, value });
    this.preorderTraversalNode(oldNode.left, arr);
    this.preorderTraversalNode(oldNode.right, arr);
  }

  inorderTraversal() {
    const dataArr = [];
    this.inorderTraversalNode(this.Root, dataArr);
    return dataArr;
  }
  inorderTraversalNode(oldNode, arr) {
    if (oldNode === null) return;
    this.preorderTraversalNode(oldNode.left, arr);
    const { key, value } = oldNode;
    arr.push({ key, value });
    this.preorderTraversalNode(oldNode.right, arr);
  }

  postorderTraversal() {
    const dataArr = [];
    this.postorderTraversalNode(this.Root, dataArr);
    return dataArr;
  }

  postorderTraversalNode(oldNode, arr) {
    if (oldNode === null) return;
    this.postorderTraversalNode(oldNode.left, arr);
    this.postorderTraversalNode(oldNode.right, arr);
    const { key, value } = oldNode;
    arr.push({ key, value });
  }

  min() {
    if (!this.Root) return false;
    let node = this.Root;
    while (node.left !== null) {
      node = node.left;
    }
    return node;
  }
  max() {
    if (!this.Root) return false;
    let node = this.Root;
    while (node.right !== null) {
      node = node.right;
    }
    return node;
  }
   remove(key) {
    let currentNode = this.Root;
    let parentNode = null;
    // 默认这个key是左节点
    let isLeftChild = true;

    // 查找节点
    while (currentNode.key !== key) {
      parentNode = currentNode;

      // 小于往左查找
      if (key < currentNode.key) {
        isLeftChild = true;
        currentNode = currentNode.left;
      } else {
        isLeftChild = false;
        currentNode = currentNode.right;
      }
      // 如果到最后都没找到key,返回false
      if (currentNode === null) {
        return false;
      }
    }
    // 删除叶子节点
    if (currentNode.left === null && currentNode.right === null) {
      if (currentNode === this.Root) {
        this.Root = null;
      } else if (isLeftChild) {
        parentNode.left = null;
      } else {
        parentNode.right = null;
      }
    }

    // 删除只有一个子节点的节点
    else if (currentNode.right === null) {
      if (currentNode === this.Root) {
        this.Root = currentNode.left;
      } else if (isLeftChild) {
        parentNode.left = currentNode.left;
      } else {
        parentNode.right = currentNode.left;
      }
    } else if (currentNode.left === null) {
      if (currentNode === this.Root) {
        this.Root = currentNode.right;
      } else if (isLeftChild) {
        parentNode.left = currentNode.right;
      } else {
        parentNode.right = currentNode.left;
      }
    }
    // 删除有两个子节点的节点
    else {
      // 找到后续节点
      let successor = this.getSuccessor(currentNode);

      // p、判断是否为根节点
      if (currentNode === this.Root) {
        this.Root = successor;
      } else if (isLeftChild) {
        parentNode.left = successor;
      } else {
        parentNode.right = successor;
      }
      // 将后续节点改为被删除的左节点
      successor.left = currentNode.left;
    }
  }
  // 获取后续节点,即从要删除的节点的右边开始查找最小的值
  getSuccessor(delNode) {
    // 定义变量,保存要找到的后续
    let successor = delNode;
    let current = delNode.right;
    let successorParent = delNode;

    // 循环查找 current 的右子树节点
    while (current !== null) {
      successorParent = successor;
      successor = current;
      current = current.left;
    }

    // 判断寻找到的后续节点是否直接就是要删除节点的 right
    if (successor !== delNode.right) {
      successorParent.left = successor.right;
      successor.right = delNode.right;
    }
    return successor;
  }

}