# 一、树结构

# 1、认知

  树结构 的应用是非常广泛的,常见的 家族谱、文件目录、公司组织架构等都是树结构。

  在我们前端开发中,DOM Tree 更为典型。

# 2、特点

  各个方面效率都还可以O(log n)

  相对于哈希表,树结构空间利用率较高、元素可以是有序的、并且可以快速的找到数组的最值

# 3、术语

  • 树:n(n>=0)个节点构成的有限集合

n = 0 时,我们称为空树

  • 节点:

root 根节点、sibling 兄弟节点、leaf node 叶子节点、parent children 父子节点

  • 度:

Edge 节点的度、Height 树的度、Level 节点的层次

# 4、表示法

常规表示法
{
  element: A,
  nextArr: []
}
儿子-兄弟表示法

  提示:儿子-兄弟表示法 相较于 常规表示法,它巧妙的将所有节点的子节点都固定成了至多两个(也就是下面要讲的 二叉树)

{
  element: A,
  childNext: BNode
  siblingNext: null
}

# 5、分类

  具体可以阅读:Useful Tree Data Structures Worth Knowing (opens new window)

  • General tree
  • Binary tree 二叉树
  • Binary search tree 二叉搜索树(BST)
  • AVL tree 平衡树(AVL)
  • Red-black tree 平衡树(红黑树)
  • Splay tree
  • Treap
  • B-tree

# 二、二叉树

  树中的每个节点最多有两个子节点,我们称这要的树为二叉树。

提示

  在二叉树中(非空),有一个非常有意思的规律:

left node 叶子节点 = other node 非叶子节点 + 1

# 1、特性

  由于二叉树的特性,我们可以知道:

  • 第 i 层(Level)最多可以有 2^(i-1) 个节点
  • 树的度(Height)为 k 时,该树总共最多可以有 2^k - 1 个节点 (完美二叉树 Perfect Binary Tree)

# 2、分类

  关于二叉树的基础认知,我们可以阅读Tree Data Structures in JavaScript for Beginners (opens new window)

  • 满二叉树 Full Binary Tree:每个节点恰好有 0 或 2 个子节点(但绝不是 1)

  • 完全二叉树 Complete Binary Tree:除最后一层之外的所有层级 Level 都充满节点

堆的本质就是一颗完全二叉树

  • 完美二叉树 Perfect Binary Tree:所有级别(包括最后一层)都充满节点时

完美二叉树是一种特殊的完全二叉树

# 3、完全二叉树

  按照从上至下,从左到右顺序存储

# 三、BST

# 1、图示

参考:https://en.m.wikipedia.org/wiki/File:Binary_search_tree.svg

  例如:在二叉搜索树中添加一个 数据 4

# 2、特点

  二叉搜索树是一种特殊的二叉树,它在原二叉树的基础上加了一条限制条件:

左子节点的值 始终小于 右子节点的值

  正是因为这个限制条件,在二叉搜索树中,我们可以很快的找到目标数据。

# 3、设计实现

参考:How to Print a Binary Tree (opens new window)Implementing a Binary Tree in Java (opens new window)

import { ITreeNode } from '../types/INode'

class TreeNode<T> implements ITreeNode<T> {
  element: T
  left: TreeNode<T> | null = null
  right: TreeNode<T> | null = null

  constructor(element: T) {
    this.element = element
  }
}

class BSTree<T> {
  private root: TreeNode<T> | null = null

  // 供打印树结构使用
  get rootNode(): TreeNode<T> | null {
    return this.root
  }

  // -----------------------
  // -----------------------

  // 递归插入新节点
  private insertNode(rootNode: TreeNode<T>, newNode: TreeNode<T>) {
    // 判断是否放在 左子树上
    if (newNode.element < rootNode.element) {
      // 判断左子树上 是否有TreeNode节点
      if (rootNode.left !== null) {
        this.insertNode(rootNode.left, newNode)
      } else {
        rootNode.left = newNode // 成功插入TreeNode节点
      }
    } else {
      if (rootNode.right !== null) {
        this.insertNode(rootNode.right, newNode)
      } else {
        rootNode.right = newNode // 成功插入TreeNode节点
      }
    }
  }

  // -----------------------
  // -----------------------

  // 插入新节点
  insert(element: T) {
    const newNode = new TreeNode(element)

    // 是否为空树
    if (this.root === null) {
      this.root = newNode
    } else {
      this.insertNode(this.root, newNode)
    }
  }
}

export default BSTree

# 四、BST 基础操作

# 1、遍历方式

  以上面二叉搜索树的示例图为例,不同的遍历结果如下:

  • 先序遍历

访问顺序:根节点 --> 左子树 --> 右子树
遍历结果:8、3、1、6、4、7、10、14、13

  • 中序遍历

访问顺序:左子树 --> 根节点 --> 右子树
遍历结果:1、3、4、6、7、8、10、13、14

  • 后序遍历

访问顺序:左子树 --> 右子树 --> 根节点
遍历结果:1、4、7、6、3、13、14、10、8

  • 层序遍历

按照 Level 层次依次访问

# 2、BST 的遍历

  • 递归、栈遍历
先序遍历
class BSTree<T> {
  // ……

  // 先序遍历
  preOderTraverse(): T[] {
    const resultArr: T[] = []

    // 递归也是一个调用栈
    function traverseBSTree(rootNode: TreeNode<T> | null) {
      if (rootNode) {
        resultArr.push(rootNode.element) // 先打印 rootNode 根节点
        traverseBSTree(rootNode.left) // 打印 左子树节点
        traverseBSTree(rootNode.right) // 打印 右子树节点
      }
    }
    traverseBSTree(this.root)

    return resultArr
  }

  // ……
}

export default BSTree
中序遍历
class BSTree<T> {
  // ……

  // 中序遍历
  inOrderTraverse(): T[] {
    const resultArr: T[] = []

    // 递归也是一个调用栈
    function traverseBSTree(rootNode: TreeNode<T> | null) {
      if (rootNode) {
        traverseBSTree(rootNode.left) // 打印 左子树节点
        resultArr.push(rootNode.element) // 先打印 rootNode 根节点
        traverseBSTree(rootNode.right) // 打印 右子树节点
      }
    }
    traverseBSTree(this.root)

    return resultArr
  }

  // ……
}

export default BSTree
后序遍历
class BSTree<T> {
  // ……

  // 后序遍历
  postOrderTraverse(): T[] {
    const resultArr: T[] = []

    // 递归也是一个调用栈
    function traverseBSTree(rootNode: TreeNode<T> | null) {
      if (rootNode) {
        traverseBSTree(rootNode.left) // 打印 左子树节点
        traverseBSTree(rootNode.right) // 打印 右子树节点
        resultArr.push(rootNode.element) // 先打印 rootNode 根节点
      }
    }
    traverseBSTree(this.root)

    return resultArr
  }

  // ……
}

export default BSTree
  • 队列遍历
层序遍历
class BSTree<T> {
  // ……

  // 层序遍历
  levelOrderTraverse(): T[] {
    const resultArr: T[] = []

    if (!this.root) return []

    // 初始化队列
    const queue: TreeNode<T>[] = []
    queue.push(this.root) // 入队

    // 若有子节点,出队时先将其左右子节点入队
    while (queue.length) {
      // 记录将要出队的 TreeNode
      const temp = queue.shift()!
      resultArr.push(temp?.element) // 打印节点

      // 将其左右子节点入队
      if (temp.left) {
        queue.push(temp.left)
      }
      if (temp.right) {
        queue.push(temp.right)
      }
    }

    return resultArr
  }

  // ……
}

export default BSTree

# 3、BST 的最值

  • 最大值、最小值
class BSTree<T> {
  // ……

  getMaxElement(): T | null {
    let temp = this.root

    while (temp && temp.right) {
      temp = temp.right
    }

    return temp?.element ?? null
  }

  getMixElement(): T | null {
    let temp = this.root

    while (temp && temp.left) {
      temp = temp.left
    }

    return temp?.element ?? null
  }

  // ……
}

export default BSTree

# 4、BST 搜索节点

  • 是否存在某个值
class BSTree<T> {
  // ……

  getElement(checkElement: T): boolean {
    let temp = this.root

    while (temp !== null) {
      if (temp.element === checkElement) return true

      if (temp.element < checkElement) {
        temp = temp.right
      } else {
        temp = temp.left
      }
    }

    return false
  }

  // ……
}

export default BSTree

# 5、BST 删除节点

  对 BST 的节点进行删除操作,大致有下面记住情况:

  • 删除的是叶子节点,例如:1、13
  • 删除的节点有一个子节点,例如:14、10
  • 删除的节点有两个子节点,例如:6、3(难点:删除后的位置填补)

  删除 3 时,我们可以采用下面两种节点进行补位:

  • 前驱节点(节点 3 的左子树中的最大值) 1 进行补位
  • 后继节点(节点 3 的右子树中的最小值) 4 进行补位
class TreeNode<T> implements ITreeNode<T> {
  element: T
  left: TreeNode<T> | null = null
  right: TreeNode<T> | null = null

  // 在删除节点时使用
  parent: ITreeNode<T> | null = null
  get isLeft(): boolean {
    return !!(this.parent && this.parent.left === this)
  }
  get isRight(): boolean {
    return !!(this.parent && this.parent.right === this)
  }

  constructor(element: T) {
    this.element = element
  }
}
class BSTree<T> {
  // ……

  // 根据element查找目标节点
  private searchNode(checkElement: T): TreeNode<T> | null {
    let currentNode = this.root
    let parentNode: TreeNode<T> | null = null

    while (currentNode !== null) {
      if (currentNode.element === checkElement) return currentNode

      // 记录当前节点
      parentNode = currentNode

      // 下移到子节点
      if (currentNode.element < checkElement) {
        currentNode = currentNode.right
      } else {
        currentNode = currentNode.left
      }

      // 追加子节点的parent信息
      if (currentNode) currentNode.parent = parentNode
    }

    return null
  }

  // 根据目标节点查找其后继节点
  private searchSuccessorNode(targetElement: TreeNode<T>): TreeNode<T> | null {
    // 后继节点:targetElement的右子树中的最大值

    // 右子树
    let currentNode = targetElement.right

    // 后继节点
    let successorNode: TreeNode<T> | null = null

    while (currentNode) {
      successorNode = currentNode // 左子树节点
      currentNode = currentNode.left // 若左子树节点有左子树节点

      if (currentNode) {
        currentNode.parent = successorNode
      }
    }

    // 数据填充
    successorNode!.left = targetElement.left
    if (successorNode !== targetElement.right) {
      successorNode!.parent!.left = successorNode!.right // 若后继节点有右子节点
      successorNode!.right = targetElement.right
    }

    return successorNode
  }

  getElement(checkElement: T): boolean {
    const isExist = !!this.searchNode(checkElement)

    return isExist
  }

  removeElement(targetElement: T): boolean {
    const currentNode = this.searchNode(targetElement)

    if (currentNode === null) return false

    // 待赋值的treenode节点
    let nodeAssignment: TreeNode<T> | null = null

    // ---------

    // 删除的是叶子节点
    if (currentNode.left === null && currentNode.right === null) {
      nodeAssignment = null
    }
    // 删除的节点有一个子节点(左子节点)
    else if (currentNode.right === null) {
      nodeAssignment = currentNode.left
    }
    // 删除的节点有一个子节点(右子节点)
    else if (currentNode.left === null) {
      nodeAssignment = currentNode.right
    }
    // 删除的节点有两个子节点
    else {
      const successorNode = this.searchSuccessorNode(currentNode)

      nodeAssignment = successorNode
    }

    // ---------

    // 正式执行删除操作
    if (currentNode === this.root) this.root = nodeAssignment // 删除的是根节点
    if (currentNode.isLeft) currentNode.parent!.left = nodeAssignment
    if (currentNode.isRight) currentNode.parent!.right = nodeAssignment

    return true
  }

  // ……
}

export default BSTree
更新于 : 7/8/2024, 10:21:14 AM