数据结构学习-二叉树(Binary Tree)

356 阅读2分钟

前面介绍的 是普通树,父节点可以有多个子节点。二叉树是父节点最多只有两个子节点的树。子节点中左边的节点称为左节点,右边的节点称为右节点。

下图就是一棵二叉树

实现

代码实现其实和普通树差不多,只是子节点需要修改一下,下面是实现代码:

class BinaryNode<Element> {
    
    var value: Element
    var leftChild: BinaryNode?  // 左节点
    var rightChild: BinaryNode? // 右节点
    
    init(value: Element) {
        self.value = value
    }
}

上面二叉树结构图的代码:

var tree: BinaryNode<Int> = {
    var zero = BinaryNode(value: 0)
    var two = BinaryNode(value: 2)
    var three = BinaryNode(value: 3)
    var five = BinaryNode(value: 5)
    var seven = BinaryNode(value: 7)
    var eight = BinaryNode(value: 8)
    
    two.leftChild = zero
    two.rightChild = three
    
    five.leftChild = two
    five.rightChild = seven
    
    seven.rightChild = eight
    
    return five
}()

一个辅助输出

为了更好的查看二叉树的输出,下面二叉树的辅助输出代码:

extension BinaryNode: CustomStringConvertible {
    
    public var description: String {
        diagram(for: self)
    }
    
    private func diagram(for node: BinaryNode?,
                         _ top: String = "",
                         _ root: String = "",
                         _ bottom: String = "") -> String {
        guard let node = node else {
            return root + "nil\n"
        }
        if node.leftChild == nil && node.rightChild == nil {
            return root + "\(node.value)\n"
        }
        return diagram(for: node.rightChild,
                       top + " ", top + "┌──", top + "│ ")
            + root + "\(node.value)\n"
            + diagram(for: node.leftChild,
                      bottom + "│ ", bottom + "└──", bottom + " ")
    }
}

查看刚刚创建的树是这样的:

     print(tree)

输出:

 ┌──8
┌──7
│ └──nil
5
│ ┌──3
└──2
 └──0

遍历

在前面介绍树的时候我们实现了深度优先遍历和层级优先遍历。下面对二叉树实现不一样的遍历。

中序遍历

  • 如果当前节点有左节点,递归遍历访问完左节点;

  • 访问当前节点;

  • 如果当前节点有右节点,递归遍历访问完右节点。

访问顺序为:左节点 --> 当前节点 --> 右节点。

实现:

extension BinaryNode {
    func traverseInOrder(visit: (Element) -> Void) {
        leftChild?.traverseInOrder(visit: visit)
        visit(value)
        rightChild?.traverseInOrder(visit: visit)
    }
}

测试:

   tree.traverseInOrder(visit: { print($0) })

输出:

0
2
3
5
7
8

前序遍历

  • 访问当前节点;

  • 如果当前节点有左节点,递归遍历访问完左节点;

  • 如果当前节点有右节点,递归遍历访问完右节点。

访问顺序为:当前节点 --> 左节点 --> 右节点。

实现:

extension BinaryNode {
    func traversePreOrder(visit: (Element) -> Void) {
        visit(value)
        leftChild?.traversePreOrder(visit: visit)
        rightChild?.traversePreOrder(visit: visit)
    }
}

测试:

   tree.traversePreOrder(visit: { print($0) })

输出:

5
2
0
3
7
8

后序遍历

  • 如果当前节点有左节点,递归遍历访问完左节点;

  • 如果当前节点有右节点,递归遍历访问完右节点;

  • 访问当前节点。

访问顺序为:左节点 --> 右节点 --> 当前节点。

实现:

extension BinaryNode {
    func traversePostOrder(visit: (Element) -> Void) {
        leftChild?.traversePostOrder(visit: visit)
        rightChild?.traversePostOrder(visit: visit)
        visit(value)
    }
}

测试:

   tree.traversePostOrder(visit: { print($0) })

输出:

0
3
2
8
7
5

练习

计算树的高度:

如果只有一个根节点的树,高度为0;

如果有一个子节点的树,高度为1.

实现的关键是递归计算左节点和右节点的最大高度

实现:

extension BinaryNode {

    // O(n)
    var height: Int {
        if leftChild == nil && rightChild == nil { return 0 }
        return 1 + max(leftChild?.height ?? 0, rightChild?.height ?? 0)
    }
}