二叉搜索树的定义
左子树节点的值都小于根节点的值,右子树节点的值都大于根节点的值
二叉搜索树的性质
- 若任意节点的左子树不空,则左子树所有节点的值小于根节点的值
- 若任意节点的右子树不空,则左子树所有节点的值大于根节点的值
- 任意节点的左右子树也为二叉搜索树
- 没有键值相等的节点
代码
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)
删除节点
删除节点很容易。不过删除节点后,需要将节点替换为左侧最大的子节点或右侧的最小子节点。这样,树在删除节点后仍然排序
//移除一个节点
@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)