二叉树遍历(前序、中序、后序、层次、深度优先、广度优先遍历) 红太狼 2022-07-11 13:25 136阅读 0赞 二叉树是一种非常重要的[数据结构][Link 1],很多其它数据结构都是基于二叉树的基础演变而来的。对于二叉树,有深度遍历和广度遍历,深度遍历有前序、中序以及后序三种遍历方法,广度遍历即我们平常所说的层次遍历。因为树的定义本身就是递归定义,因此采用递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁,而对于广度遍历来说,需要其他数据结构的支撑,比如堆了。所以,对于一段代码来说,可读性有时候要比代码本身的效率要重要的多。 四种主要的遍历思想为: 前序遍历:根结点 ---> 左子树 ---> 右子树 中序遍历:左子树\---> 根结点 \---> 右子树 后序遍历:左子树 ---> 右子树 \---> 根结点 层次遍历:只需按层次遍历即可 例如,求下面二叉树的各种遍历 ![Center][] ![Image 1][] 前序遍历:1 2 4 5 7 8 3 6 中序遍历:4 2 7 5 8 1 3 6 后序遍历:4 7 8 5 2 6 3 1 层次遍历:1 2 3 4 5 6 7 8 一、前序遍历 1)根据上文提到的遍历思路:根结点 ---> 左子树 ---> 右子树,很容易写出递归版本: **\[java\]** [view plain][] [copy][view plain] 1. **public****void** preOrderTraverse1(TreeNode root) \{ 2. **if** (root != **null**) \{ 3. System.out.print(root.val+" "); 4. preOrderTraverse1(root.left); 5. preOrderTraverse1(root.right); 6. \} 7. \} 2)现在讨论非递归的版本: 根据前序遍历的顺序,优先访问根结点,然后在访问左子树和右子树。所以,对于任意结点node,第一部分即直接访问之,之后在判断左子树是否为空,不为空时即重复上面的步骤,直到其为空。若为空,则需要访问右子树。注意,在访问过左孩子之后,需要反过来访问其右孩子,所以,需要栈这种数据结构的支持。对于任意一个结点node,具体步骤如下: a)访问之,并把结点node入栈,当前结点置为左孩子; b)判断结点node是否为空,若为空,则取出栈顶结点并出栈,将右孩子置为当前结点;否则重复a)步直到当前结点为空或者栈为空(可以发现栈中的结点就是为了访问右孩子才存储的) 代码如下: **\[java\]** [view plain][] [copy][view plain] 1. **public****void** preOrderTraverse2(TreeNode root) \{ 2. LinkedList<TreeNode> stack = **new** LinkedList<>(); 3. TreeNode pNode = root; 4. **while** (pNode != **null** || !stack.isEmpty()) \{ 5. **if** (pNode != **null**) \{ 6. System.out.print(pNode.val+" "); 7. stack.push(pNode); 8. pNode = pNode.left; 9. \} **else** \{ //pNode == null && !stack.isEmpty() 10. TreeNode node = stack.pop(); 11. pNode = node.right; 12. \} 13. \} 14. \} 二、中序遍历 1)根据上文提到的遍历思路:左子树 \---> 根结点 \---> 右子树,很容易写出递归版本: **\[java\]** [view plain][] [copy][view plain] 1. **public****void** inOrderTraverse1(TreeNode root) \{ 2. **if** (root != **null**) \{ 3. inOrderTraverse1(root.left); 4. System.out.print(root.val+" "); 5. inOrderTraverse1(root.right); 6. \} 7. \} 2)非递归实现,有了上面前序的解释,中序也就比较简单了,相同的道理。只不过访问的顺序移到出栈时。代码如下: **\[java\]** [view plain][] [copy][view plain] 1. **public****void** inOrderTraverse2(TreeNode root) \{ 2. LinkedList<TreeNode> stack = **new** LinkedList<>(); 3. TreeNode pNode = root; 4. **while** (pNode != **null** || !stack.isEmpty()) \{ 5. **if** (pNode != **null**) \{ 6. stack.push(pNode); 7. pNode = pNode.left; 8. \} **else** \{ //pNode == null && !stack.isEmpty() 9. TreeNode node = stack.pop(); 10. System.out.print(node.val+" "); 11. pNode = node.right; 12. \} 13. \} 14. \} 三、后序遍历 1)根据上文提到的遍历思路:左子树 ---> 右子树 \---> 根结点,很容易写出递归版本: **\[java\]** [view plain][] [copy][view plain] 1. **public****void** postOrderTraverse1(TreeNode root) \{ 2. **if** (root != **null**) \{ 3. postOrderTraverse1(root.left); 4. postOrderTraverse1(root.right); 5. System.out.print(root.val+" "); 6. \} 7. \} 2) 后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍两种思路。 第一种思路:对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问,因此其右孩子还为被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶。 ![复制代码][copycode.gif] void postOrder2(BinTree *root) //非递归后序遍历 { stack<BTNode*> s; BinTree *p=root; BTNode *temp; while(p!=NULL||!s.empty()) { while(p!=NULL) //沿左子树一直往下搜索,直至出现没有左子树的结点 { BTNode *btn=(BTNode *)malloc(sizeof(BTNode)); btn->btnode=p; btn->isFirst=true; s.push(btn); p=p->lchild; } if(!s.empty()) { temp=s.top(); s.pop(); if(temp->isFirst==true) //表示是第一次出现在栈顶 { temp->isFirst=false; s.push(temp); p=temp->btnode->rchild; } else //第二次出现在栈顶 { cout<<temp->btnode->data<<" "; p=NULL; } } } } ![复制代码][copycode.gif] 第二种思路:要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。 ![复制代码][copycode.gif] void postOrder3(BinTree *root) //非递归后序遍历 { stack<BinTree*> s; BinTree *cur; //当前结点 BinTree *pre=NULL; //前一次访问的结点 s.push(root); while(!s.empty()) { cur=s.top(); if((cur->lchild==NULL&&cur->rchild==NULL)|| (pre!=NULL&&(pre==cur->lchild||pre==cur->rchild))) { cout<<cur->data<<" "; //如果当前结点没有孩子结点或者孩子节点都已被访问过 s.pop(); pre=cur; } else { if(cur->rchild!=NULL) s.push(cur->rchild); if(cur->lchild!=NULL) s.push(cur->lchild); } } } ![复制代码][copycode.gif] 四、层次遍历 层次遍历的代码比较简单,只需要一个队列即可,先在队列中加入根结点。之后对于任意一个结点来说,在其出队列的时候,访问之。同时如果左孩子和右孩子有不为空的,入队列。代码如下: **\[java\]** [view plain][] [copy][view plain] 1. **public****void** levelTraverse(TreeNode root) \{ 2. **if** (root == **null**) \{ 3. **return**; 4. \} 5. LinkedList<TreeNode> queue = **new** LinkedList<>(); 6. queue.offer(root); 7. **while** (!queue.isEmpty()) \{ 8. TreeNode node = queue.poll(); 9. System.out.print(node.val+" "); 10. **if** (node.left != **null**) \{ 11. queue.offer(node.left); 12. \} 13. **if** (node.right != **null**) \{ 14. queue.offer(node.right); 15. \} 16. \} 17. \} 五、深度优先遍历 其实深度遍历就是上面的前序、中序和后序。但是为了保证与广度优先遍历相照应,也写在这。代码也比较好理解,其实就是前序遍历,代码如下: **\[java\]** [view plain][] [copy][view plain] 1. **public****void** depthOrderTraverse(TreeNode root) \{ 2. **if** (root == **null**) \{ 3. **return**; 4. \} 5. LinkedList<TreeNode> stack = **new** LinkedList<>(); 6. stack.push(root); 7. **while** (!stack.isEmpty()) \{ 8. TreeNode node = stack.pop(); 9. System.out.print(node.val+" "); 10. **if** (node.right != **null**) \{ 11. stack.push(node.right); 12. \} 13. **if** (node.left != **null**) \{ 14. stack.push(node.left); 15. \} 16. \} 17. \} ![save_snippets.png][] [Link 1]: http://lib.csdn.net/base/datastructure [Center]: /images/20220711/2a0ad9f59052404a9c1b7f91274bc57c.png [Image 1]: [view plain]: http://blog.csdn.net/my_jobs/article/details/43451187# [copycode.gif]: /images/20220711/36a0b44d36ae43c38cecae0c19f6e458.png [save_snippets.png]: /images/20220711/4fbd4169c399415ab6e2a0b4ee20faf2.png
还没有评论,来说两句吧...