一颗二叉树被构造函数(数组创建一棵树)、拷贝构造函数或者拷贝赋值函数创建以后,我们现在要对这棵树进行一些遍历操作,找出这棵树上的所有节点。

二叉树的搜索策略已经看过了,共有4种

  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层次遍历

二叉树的前序、中序和后序遍历都需要借助栈stack这种结构,而层次遍历需要借助队列queue

2.1 前序遍历

二、遍历二叉树 - 图1 框架

框架如下

  1. template<typename T>
  2. void binary_tree<T>::print_pre_order(void) const {
  3. }

二、遍历二叉树 - 图2 遍历过程

前序遍历规则:根—>左—>右,现在根据这个规则走一遍下面这棵树

二、遍历二叉树 - 图3

走一遍的结果为:a -> b -> d -> e -> f -> c

二、遍历二叉树 - 图4 代码

实现上述过程代码为

  1. template<typename T>
  2. void binary_tree<T>::print_pre_order(void) const {
  3. stack<tree_node*> node_stack;
  4. if (root_ != 0) {
  5. node_stack.push(root_);
  6. tree_node* sorc_node;
  7. while(!node_stack.empty()) {
  8. sorc_node = node_stack.top();
  9. node_stack.pop();
  10. if (sorc_node->rchild != 0) {
  11. node_stack.push(sorc_node->rchild);
  12. }
  13. if (sorc_node->lchild != 0) {
  14. node_stack.push(sorc_node->lchild);
  15. }
  16. } // while
  17. } // if
  18. }

上面的这几行代码虽然很少,但是非常重要,即便是以后在图结构中要使用到的深度优先搜索(DFS)以及其各种衍化体型都是在这个遍历过程的基础上进行改变的,建议好好理解这个实现的过程 二、遍历二叉树 - 图5

2.2 中序遍历

二、遍历二叉树 - 图6 框架

框架如下

  1. template <typename T>
  2. void binary_tree<T>::print_in_order(void) const {
  3. }

二、遍历二叉树 - 图7 遍历过程

中序遍历规则:左—>根—>右,现在根据这个规则走一遍下面这棵树

二、遍历二叉树 - 图8

走一遍的结果为:d -> e -> b -> f -> a -> c

二、遍历二叉树 - 图9 代码

相比于前序遍历,中序遍历的实现就复杂了一点,因为它不是从根节点开始的,而是从左子节点开始的,所以需要先遍历根节点的所有左子节点

  1. template <typename T>
  2. void binary_tree<T>::print_in_order(void) const {
  3. stack<tree_node*> node_stack;
  4. tree_node* lft_node = root_, tmp_node;
  5. while (lft_node->lchild != 0) { // 遍历找左子节点
  6. node_stack.push(lft_node);
  7. lft_node = lft_node->lchild;
  8. }
  9. if (!node_stack.empty()) {
  10. tmp_node = node_stack.top();
  11. node_stack.pop();
  12. if (tmp_node->rchild != 0) {
  13. node_stack.push(tmp_node->rchild);
  14. tmp_node = node_stack.top();
  15. node_stack.pop();
  16. while (tmp_node->lchild != 0) { // 注意:遍历找右节点的左子节点
  17. node_stack.push(tmp_node);
  18. tmp_node = tmp_node->lchild;
  19. }
  20. }
  21. else {
  22. cout << tmp_node->data << endl; // 访问的元素
  23. }
  24. }
  25. }

上面有一个需要注意的事情,就是该二叉树的右节点有左子节点的情况下,需要做遍历将左子节点放进去,这个过程就是之前的遍历找左子节点的过程。

二、遍历二叉树 - 图10

2.3 后序遍历

二、遍历二叉树 - 图11 框架

框架如下

  1. template <typename T>
  2. void binary_tree<T>::print_post_order(void) const {
  3. }

二、遍历二叉树 - 图12 遍历过程

后序遍历规则:左—>右—>根,现在根据这个规则走一遍下面这棵树

二、遍历二叉树 - 图13

走一遍的结果为:e -> d -> f -> b -> c -> a

二、遍历二叉树 - 图14 代码

后序遍历的规则比较好玩:对每个根节点判断是否有左子节点,如果有的话

  1. template<typename T>
  2. void binary_tree<T>::print_post_order(void) const {
  3. // 后续序序遍历输出一颗树的全部结点值2,3,1
  4. // 广度优先遍历
  5. cout << "print_post_order : ";
  6. typedef pair<tree_node*, bool> multinode;
  7. stack<multinode> tempstack;
  8. if (root_) {
  9. tempstack.push(make_pair(root_, false));
  10. }
  11. while (!tempstack.empty()) {
  12. multinode m = tempstack.top(); tempstack.pop();
  13. if (m.first->lchild == NULL && m.first->rchild == NULL) {// 叶子节点直接输出
  14. cout << m.first->data << " ";
  15. }
  16. else if (m.second == true) { // 所有孩子都遍历完了才会到这一步
  17. cout << m.first->data << " ";
  18. }
  19. else { // 非终结点,并且孩子还没遍历完。
  20. m.second = true; tempstack.push(m);
  21. if (m.first->rchild != NULL) {
  22. tempstack.push(make_pair(m.first->rchild, false));
  23. }
  24. if (m.first->lchild != NULL) {
  25. tempstack.push(make_pair(m.first->lchild, false));
  26. }
  27. }
  28. }
  29. cout << endl;
  30. }

因为栈是先进后出的,所以会对之前的每个节点进行判断是否有右子节点,左->右 ->根的顺序其根必然有右节点,此时就会陷入死循环。所以需要一个外力帮助它打破这个死循环。

这个外力就是typedef pair<tree_node*, bool> multinode;中的bool类型的判断值,用来判断这个根是否可以被弹出。