数据结构10-二叉树的遍历

上一篇已经实现二叉树的基本结构,而且在二叉树的基本上实现了二叉查找树的添加功能,接下来我们在二叉查找树的基础上,创建一个包含具体元素二叉树,在此基础上实现二叉树的各种遍历方式。

二叉树的遍历

二叉树的遍历属于二叉树共同的性质,这里将遍历方法放在二叉树之前封装好的二叉树的基类JKRBinaryTree中,二叉树的遍历的接口如下:

- (void)enumerateObjectsWithTraversalType:(JKRBinaryTreeTraversalType)traversalType usingBlock:(void (^)(ObjectType obj, BOOL *stop))block;
复制代码

模仿数组的遍历方法,通过 traversalType 决定使用哪种遍历方法,在block中返回遍历的元素,并通过控制stop决定是否停止遍历。

内部traversalType去调用不同的遍历方式:

- (void)enumerateObjectsWithTraversalType:(JKRBinaryTreeTraversalType)traversalType usingBlock:(void (^)(id _Nonnull, BOOL * _Nonnull))block {
    switch (traversalType) {
        case JKRBinaryTreeTraversalTypePreorder:
            [self enumerateObjectsWithPreorderTraversalUsingBlock:block];
            break;
        case JKRBinaryTreeTraversalTypeInorder:
            [self enumerateObjectsWithInorderTraversalUsingBlock:block];
            break;
        case JKRBinaryTreeTraversalTypePostorder:
            [self enumerateObjectsWithPostorderTraversalUsingBlock:block];
            break;
        case JKRBinaryTreeTraversalTypeLevelOrder:
            [self enumerateObjectsWithLevelOrderTraversalUsingBlock:block];
            break;
        default:
            [self enumerateObjectsWithInorderTraversalUsingBlock:block];
            break;
    }
}
复制代码

遍历可以采用递归和非递归的方式分别实现,但是实际还是使用非递归的方式,因为递归速度更慢,而且函数直接的会占用栈内存空间,原因取决于汇编中函数调用的寄存器恢复与保护,参见汇编语言中的函数调用

前序遍历

访问根结点的操作发生在遍历其左右子树之前。前序遍历首先访问根结点然后遍历左子树,最后遍历右子树,而且子树也是用同样的方式遍历,如下二叉树,前序遍历的顺序为:

递归实现

- (void)enumerateObjectsWithPreorderTraversalUsingBlock:(void (^)(id _Nonnull, BOOL * _Nonnull))block {
    __block BOOL stop = NO;
    [self preorderTraversal:_root block:^(id element) {
        block(element, &stop);
    } stop:&stop];
}

- (void)preorderTraversal:(JKRBinaryTreeNode *)node block:(orderBlock)block stop:(BOOL *)stop {
    if (node && !*stop) {
        block(node.object);
        [self preorderTraversal:node.left block:block stop:stop];
        [self preorderTraversal:node.right block:block stop:stop];
    }
}
复制代码

非递归实现

- (void)enumerateObjectsWithPreorderTraversalUsingBlock:(void (^)(id _Nonnull, BOOL * _Nonnull))block {
    __block BOOL stop = NO;
    
    [self  preorderTraversalWithBlock:^(id element) {
        block(element, &stop);
    } stop:&stop];
}

- (void)preorderTraversalWithBlock:(orderBlock)block stop:(BOOL *)stop {
    if (_root) {
        JKRStack *stack = [JKRStack new];
        [stack push:_root];
        while (stack.count && !*stop) {
            JKRBinaryTreeNode *n = [stack pop];
            block(n.object);
            if (n.right) [stack push:n.right];
            if (n.left) [stack push:n.left];
        }
    }
}
复制代码

中序遍历

访问根结点的操作发生在遍历其左右子树之中。中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。

递归实现

- (void)enumerateObjectsWithInorderTraversalUsingBlock:(void (^)(id _Nonnull, BOOL * _Nonnull))block {
    __block BOOL stop = NO;
    [self inorderTraversal:_root block:^(id element) {
        block(element, &stop);
    } stop:&stop];
}


- (void)inorderTraversal:(JKRBinaryTreeNode *)node block:(orderBlock)block stop:(BOOL *)stop {
    if (node && !*stop) {
        [self inorderTraversal:node.left block:block stop:stop];
        if (*stop) return;
        block(node.object);
        [self inorderTraversal:node.right block:block stop:stop];
    }
}
复制代码

非递归实现

- (void)enumerateObjectsWithInorderTraversalUsingBlock:(void (^)(id _Nonnull, BOOL * _Nonnull))block {
    __block BOOL stop = NO;
    
    [self inorderTraversalWithBlock:^(id element) {
        block(element, &stop);
    } stop:&stop];
}

- (void)inorderTraversalWithBlock:(orderBlock)block stop:(BOOL *)stop {
    if (!_root) return;
    JKRBinaryTreeNode *node = _root;
    JKRStack *stack = [JKRStack new];
    do {
        while (node) {
            [stack push:node];
            node = node.left;
        }
        if (stack.count) {
            JKRBinaryTreeNode *n = [stack pop];
            block(n.object);
            node = n.right;
        }
    } while((stack.count || node) && !*stop);
}
复制代码

后序遍历

访问根结点的操作发生在遍历其左右子树之后。先左后右再根,即首先遍历左子树,然后遍历右子树,最后访问根结点。

递归实现

- (void)enumerateObjectsWithPostorderTraversalUsingBlock:(void (^)(id _Nonnull, BOOL * _Nonnull))block {
    __block BOOL stop = NO;
    [self postorderTraversal:_root block:^(id element) {
        block(element, &stop);
    } stop:&stop];
}

- (void)postorderTraversal:(JKRBinaryTreeNode *)node block:(orderBlock)block stop:(BOOL *)stop {
    if (node && !*stop) {
        [self postorderTraversal:node.left block:block stop:stop];
        [self postorderTraversal:node.right block:block stop:stop];
        if (*stop) return;
        block(node.object);
    }
}
复制代码

非递归实现

- (void)enumerateObjectsWithPostorderTraversalUsingBlock:(void (^)(id _Nonnull, BOOL * _Nonnull))block {
    __block BOOL stop = NO;
    [self postorderTraversalWithBlock:^(id element) {
        block(element, &stop);
    } stop:&stop];
}

- (void)postorderTraversalWithBlock:(orderBlock)block stop:(BOOL *)stop {
    if (!_root) return;
    JKRStack *stack = [JKRStack new];
    [stack push:_root];
    JKRBinaryTreeNode *prevPopNode;
    while (stack.count && !*stop) {
        JKRBinaryTreeNode *top = stack.peek;
        if (top.isLeaf || (prevPopNode && prevPopNode.parent == top)) {
            prevPopNode = stack.pop;
            block(prevPopNode.object);
        } else {
            if (top.right) [stack push:top.right];
            if (top.left) [stack push:top.left];
        }
    }
}
复制代码

层序遍历

设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

- (void)enumerateObjectsWithLevelOrderTraversalUsingBlock:(void (^)(id _Nonnull, BOOL * _Nonnull))block {
    __block BOOL stop = NO;
    [self levelOrderTraversalWithBlock:^(id element) {
        block(element, &stop);
    } stop:&stop];
}

- (void)levelOrderTraversalWithBlock:(orderBlock)block stop:(BOOL *)stop {
    if (!_root) return;
    JKRQueue *queue = [JKRQueue new];
    [queue enQueue:_root];
    while (queue.count && !*stop) {
        for (NSInteger i = 0, n = queue.count; i < n; i++) {
            if (*stop) return;
            JKRBinaryTreeNode *n = [queue deQueue];
            block(n.object);
            if (n.left) [queue enQueue:n.left];
            if (n.right) [queue enQueue:n.right];
        }
    }
}
复制代码

二叉树遍历的应用

前序遍历应用

树状结构展示,用于获取二叉树的结构以便以树状结构打印一棵二叉树用于调试,参见二叉树中对二叉树的打印。

中序遍历应用

上面应该可以发现一个有趣的规则,在二叉搜索树中,中序遍历刚好是升序排列。

层序遍历应用

获取叉二树的高度,这里给我们封装的二叉树添加一个获取高度的方法:

- (NSUInteger)height {
    NSUInteger height = 0;
    if (_root) {
        JKRQueue *queue = [JKRQueue new];
        [queue enQueue:_root];
        while (queue.count) {
            height++;
            for (NSInteger i = 0, n = queue.count; i < n; i++) {
                JKRBinaryTreeNode *node = [queue deQueue];
                if (node.left) [queue enQueue:node.left];
                if (node.right) [queue enQueue:node.right];
            }
        }
    }
    return height;
}
复制代码

前驱节点

中序遍历时,一个节点的前一个节点。

在二叉搜索树中,前驱节点就是前一个比它小的节点。

如上二叉树中,节点元素为 11 的节点,它的前驱节点是节点元素为 10 的节点。

后继节点

中序遍历时,一个节点的后一个节点。

在二叉搜索树中,后继节点就是后一个比它大的节点。

如上二叉树中,节点元素为 9 的节点,它的后继节点是节点元素为 10 的节点。

接下来

有了二叉树遍历、前驱节点、后继节点的概念,下面就可以完成二叉搜索树的删除功能了。

源码

点击查看源码

转载于:https://juejin.im/post/5d048911518825092c716f5a

猜你喜欢

转载自blog.csdn.net/weixin_34378767/article/details/93182181