上一篇已经实现二叉树的基本结构,而且在二叉树的基本上实现了二叉查找树的添加功能,接下来我们在二叉查找树的基础上,创建一个包含具体元素二叉树,在此基础上实现二叉树的各种遍历方式。
二叉树的遍历
二叉树的遍历属于二叉树共同的性质,这里将遍历方法放在二叉树之前封装好的二叉树的基类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