Swift 数据结构 - 二叉搜索树(Binary Search Tree, BST)

二叉搜索树的定义

左子树节点的值都小于根节点的值,右子树节点的值都大于根节点的值

二叉搜索树的性质

  • 若任意节点的左子树不空,则左子树所有节点的值小于根节点的值
  • 若任意节点的右子树不空,则左子树所有节点的值大于根节点的值
  • 任意节点的左右子树也为二叉搜索树
  • 没有键值相等的节点

代码

public class BinarySearchTree {
    private(set) public var value: T
    private(set) public var parent: BinarySearchTree?
    private(set) public var left: BinarySearchTree?
    private(set) public var right: BinarySearchTree?
    
    public init(value: T) {
        self.value = value
    }
    /// 是否是根节点
    public var isRoot: Bool {
        return parent == nil
    }
    /// 是否是叶节点
    public var isLeaf: Bool {
        return left == nil && right == nil
    }
    /// 是否是左子节点
    public var isLeftChild: Bool {
        return parent?.left === self
    }
    /// 是否是右子节点
    public var isRightChild: Bool {
        return parent?.right === self
    }
    /// 是否有左子节点
    public var hasLeftChild: Bool {
        return left != nil
    }
    /// 是否有右子节点
    public var hasRightChild: Bool {
        return right != nil
    }
    /// 是否有子节点
    public var hasAnyChild: Bool {
        return hasLeftChild || hasRightChild
    }
    /// 是否左右两个子节点都有
    public var hasBothChildren: Bool {
        return hasLeftChild && hasRightChild
    }
    /// 当前节点包括子树中的所有节点总数
    public var count: Int {
        return (left?.count ?? 0) + 1 + (right?.count ?? 0)
    }
}

此类仅描述单个节点而不是整个树。 它是泛型类型,因此节点可以存储任何类型的数据。 它还包含了 left 和 right 子节点以及一个 parent节点 的引用。

基本操作

插入新节点

执行插入时,我们首先将新值与根节点进行比较。 如果新值较小,我们采取 左 分支; 如果更大,我们采取 右 分支。我们沿着这条路向下走,直到找到一个我们可以插入新值的空位。

  public func insert(value: T) {
    if value < self.value {
      if let left = left {
        left.insert(value: value)
      } else {
        left = BinarySearchTree(value: value)
        left?.parent = self
      }
    } else {
      if let right = right {
        right.insert(value: value)
      } else {
        right = BinarySearchTree(value: value)
        right?.parent = self
      }
    }
  }

为方便起见,让我们添加一个以数组的方式初始化的方法,这个方法为数组中所有元素调用insert():

  public convenience init(array: [T]) {
    precondition(array.count > 0)
    self.init(value: array.first!)
    for v in array.dropFirst() {
      insert(value: v)
    }
  }

现在可以简单的使用:

let tree = BinarySearchTree(array: [7, 2, 5, 10, 9, 1])

搜索树

要在树中查找值,我们执行与插入相同的步骤:

  • 如果该值小于当前节点,则选择左分支。
  • 如果该值大于当前节点,则选择右分支。
  • 如果该值等于当前节点,我们就找到了它!

像大多数树操作一样,这是递归执行的,直到找到我们正在查找的内容或查完要查看的所有节点。

  public func search(value: T) -> BinarySearchTree? {
    if value < self.value {
      return left?.search(value)
    } else if value > self.value {
      return right?.search(value)
    } else {
      return self  // found it!
    }
  }

测试搜索:

tree.search(value: 5)
tree.search(value: 2)
tree.search(value: 7)
tree.search(value: 6)   // nil

遍历树

有时您需要查看所有节点而不是仅查看一个节点。

遍历二叉树有三种方法:

  • 中序(或 深度优先,In-order/depth-first):首先查看节点的左子节点,然后查看节点本身,最后查看其右子节点。
  • 前序(Pre-order):首先查看节点本身,然后查看其左右子节点。
  • 后序(Post-order):首先查看左右子节点并最后处理节点本身。

遍历树的过程也是递归的,如果按 中序遍历 二叉搜索树,它会查看所有节点,并且结果是 有序 的。

三种不同的方法,如下:

//中序遍历
  public func traverseInOrder(process: (T) -> Void) {
    left?.traverseInOrder(process: process)
    process(value)
    right?.traverseInOrder(process: process)
  }
//前序遍历
  public func traversePreOrder(process: (T) -> Void) {
    process(value)
    left?.traversePreOrder(process: process)
    right?.traversePreOrder(process: process)
  }
//后序遍历
  public func traversePostOrder(process: (T) -> Void) {
    left?.traversePostOrder(process: process)
    right?.traversePostOrder(process: process)
    process(value)
  }

要打印出从低到高排序的树的所有值,您可以编写:

tree.traverseInOrder { value in print(value) }

还可以在树中添加map()filter()方法。 例如,这是map按 中序 遍历树的实现:

  public func map(formula: (T) -> T) -> [T] {
    var a = [T]()
    if let left = left { a += left.map(formula: formula) }
    a.append(formula(value))
    if let right = right { a += right.map(formula: formula) }
    return a
  }

一个非常简单的如何使用map()的例子:

  public func toArray() -> [T] {
    return map { $0 }
  }

这会将树的内容重新转换为已排序的数组:

tree.toArray()       // [1, 2, 5, 7, 9, 10]

调试输出

extension BinarySearchTree: CustomStringConvertible {
  public var description: String {
    var s = ""
    if let left = left {
      s += "(\(left.description)) <- "
    }
    s += "\(value)"
    if let right = right {
      s += " -> (\(right.description))"
    }
    return s
  }
}

当你执行print(tree)时,你应该得到:

((1) <- 2 -> (5)) <- 7 -> ((9) <- 10)
image.png

删除节点

删除节点很容易。不过删除节点后,需要将节点替换为左侧最大的子节点或右侧的最小子节点。这样,树在删除节点后仍然排序

//移除一个节点
  @discardableResult public func remove() -> BinarySearchTree? {
    let replacement: BinarySearchTree?

    // 当前节点的替换可以是左侧最大的替换  或者
    // 右边最小的那个
    if let right = right {
      replacement = right.minimum()
    } else if let left = left {
      replacement = left.maximum()
    } else {
      replacement = nil
    }

    replacement?.remove()

    // 将替换放置在当前节点的位置
    replacement?.right = right
    replacement?.left = left
    right?.parent = replacement
    left?.parent = replacement
    reconnectParentTo(node:replacement)

    // 当前节点不再是树的一部分,因此请清理它。
    parent = nil
    left = nil
    right = nil

    return replacement
  }
//重新设置一个节点
  private func reconnectParentTo(node: BinarySearchTree?) {
    if let parent = parent {
      if isLeftChild {
        parent.left = node
      } else {
        parent.right = node
      }
    }
    node?.parent = parent
  }

返回节点最小值和最大值的函数:

  public func minimum() -> BinarySearchTree {
    var node = self
    while let next = node.left {
      node = next
    }
    return node
  }

  public func maximum() -> BinarySearchTree {
    var node = self
    while let next = node.right {
      node = next
    }
    return node
  }

深度和高度

回想一下节点的高度是到最低叶节点的距离。 我们可以用以下函数来计算:

  public func height() -> Int {
    if isLeaf {
      return 0
    } else {
      return 1 + max(left?.height() ?? 0, right?.height() ?? 0)
    }
  }

计算节点的 深度,即到根节点的距离。

  public func depth() -> Int {
    var node = self
    var edges = 0
    while let parent = node.parent {
      node = parent
      edges += 1
    }
    return edges
  }

它沿着 parent 指针向上逐步穿过树,直到我们到达根节点(即parent为nil)。 这需要 O(h) 时间。

检查树是否是有效的二叉搜索树:

  public func isBST(minValue minValue: T, maxValue: T) -> Bool {
    if value < minValue || value > maxValue { 
        return false
     }
    let leftBST = left?.isBST(minValue: minValue, maxValue: value) ?? true
    let rightBST = right?.isBST(minValue: value, maxValue: maxValue) ?? true
    return leftBST && rightBST
  }

使用如下:

if let node1 = tree.search(1) {
  tree.isBST(minValue: Int.min, maxValue: Int.max)  // true
  node1.insert(100)                                 // EVIL!!!
  tree.search(100)                                  // nil
  tree.isBST(minValue: Int.min, maxValue: Int.max)  // false
}

在非根节点上调用insert()将二分搜索树变为无效树

根节点的值为7,因此值为100的节点必须位于树的右侧分支中。 但是,您不是插入根,而是插入树左侧分支中的叶节点。 所以新的100节点在树中的错误位置!

结果,tree.search(100)给出 nil。

转载文章

二叉搜索树(Binary Search Tree, BST)

你可能感兴趣的:(Swift 数据结构 - 二叉搜索树(Binary Search Tree, BST))