二叉树-遍历终极版
tielimu
8年前
<p>对于二叉树的遍历,最熟悉的就是递归遍历了,对二叉树的非递归遍历大致知道一些,但是不太熟悉,尤其是后续非递归遍历的实现,一直比较懵逼,于是上网查询了一下,果然大神无处不在,那个后序遍历的双栈法,简直让人拍案叫绝,下面总结下。</p> <h3>1、先序遍历</h3> <p>先序遍历即顺序为:根节点->左节点->右节点</p> <p>递归版:</p> <pre> <code class="language-cpp">void PreOrderTraverse(BTree T) { if (T != NULL) { cout << T->val; PreOrderTraverse(T->left); PreOrderTraverse(T->right); } }</code></pre> <p>非递归版:</p> <p>根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问完其左子树后,再访问它的右子树。因此其处理过程如下:</p> <p>对于任一结点P:</p> <p>1)访问结点P,并将结点P入栈;</p> <p>2)判断结点P的左孩子是否为空,若不为空,则将P的左孩子置为当前的结点P;若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1)</p> <p>3)直到P为NULL并且栈为空,则遍历结束。</p> <p>代码如下:</p> <pre> <code class="language-cpp">void PreOrderTraverse(BTree T) { std::stack<BTree> S; while(T||!S.empty()) { while(T) { S.push(T); cout<<T->val; T=T->left; } if(!S.empty()) { T=S.top(); S.pop(); T=T->right; } } }</code></pre> <p>上面那个递归版是常规版,这个我觉得更好,思路是这样的,节点的右左节点分别入栈,根绝栈的“先进后出”特性,那么先输出的必然是左节点。这个方法代码简洁,思路独特。</p> <pre> <code class="language-cpp">void PreOrderTraverse(BTree T) //先序遍历的非递归 { if(!T) return ; stack<BiTree> s; s.push(T); while(!s.empty()) { BiTree temp = s.top(); cout<<temp->data<<" "; s.pop(); if(temp->rchild) s.push(temp->rchild); if(temp->lchild) s.push(temp->lchild); } }</code></pre> <h3>2、中序遍历</h3> <p>中序遍历的顺序是:左节点、根节点、右节点</p> <p>递归版:</p> <pre> <code class="language-cpp">void MidOrderTraaerse(BTree T) { if(T!=NULL) { MidOrderTraverse(T->left); cout<<T->val; MideOrderTraverse(T->right); } }</code></pre> <p>非递归版:</p> <p>根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点输出节点值,按相同的规则访问其右子树。</p> <p>因此其处理过程如下:</p> <p>对于任一结点P,</p> <p>1)若节点p不为空,则将P入栈并将P的左孩子置为当前的P,然后对P再进行相同的处理;</p> <p>2)若访问到左孩子为空后,则取栈顶元素并进行出栈操作,输出该栈顶结点值,然后将当前的P置为栈顶结点的右孩子;</p> <p>3)直到P为NULL并且栈为空则遍历结束</p> <p>代码如下:</p> <pre> <code class="language-cpp">void MidOrderTraverse(BTree T) { std::stack<BTree> S; while(T||!S.empty()) { while(T) { S.push(T); T=T->eft; } T=S.top(); cout<<T->val; S.pop(); T=T->ight; } }</code></pre> <h3>3、后序遍历</h3> <p>后序遍历的顺序是:左节点、右节点、根节点</p> <p>递归版:</p> <pre> <code class="language-cpp">void BackOrderTraverse(BTree T) { if(T!=NULL) { BackOrderTraverse(T->left); BackOrderTraverse(T->right); cout<<T->val; } }</code></pre> <p>非递归版:</p> <p>1)版本1:</p> <pre> <code class="language-cpp">void PostOrder_Nonrecursive1(BTree T) // 后序遍历的非递归 { stack<BiTree> S; BiTree curr = T ; // 指向当前要检查的节点 BiTree previsited = NULL; // 指向前一个被访问的节点 while(curr != NULL || !S.empty()) // 栈空时结束 { while(curr != NULL) // 一直向左走直到为空 { S.push(curr); curr = curr->lchild; } curr = S.top(); // 当前节点的右孩子如果为空或者已经被访问,则访问当前节点 if(curr->rchild == NULL || curr->rchild == previsited) { cout<<curr->data<<" "; previsited = curr; S.pop(); curr = NULL; } else curr = curr->rchild; // 否则访问右孩子 } }</code></pre> <p>2)版本2:</p> <p>采用双栈法,这个真是神来之笔,对于想出这个办法的人,真是佩服的五体投地。为了解释原理,的看个实际例子,如下。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3ef649dadaa624f84df3140ba9eee393.jpg"></p> <p>对于上面上面的二叉树,对树先左后右的将各个节点逐个放到栈1中,然后将栈1元素放到栈2中,过程如下:</p> <p>栈1:A</p> <p>栈1:空 | 栈2: A</p> <p>栈1:B C -> B | 栈2:A C</p> <p>栈1:B F G ->B F | 栈2:A C G</p> <p>栈1:B F -> B | 栈2:A C G F</p> <p>栈1:B -> D E | 栈2:A C G F B</p> <p>栈1:D E -> D | 栈2:A C G F B E</p> <p>栈1:空 | 栈2:A C G F B E D</p> <p>最后输出栈2,就是D E B F G C A,你说6不6.</p> <pre> <code class="language-cpp">void PostOrder_Nonrecursive(BTree T) // 后序遍历的非递归 双栈法 { stack<BiTree> s1 , s2; BiTree curr ; // 指向当前要检查的节点 s1.push(T); while(!s1.empty()) // 栈空时结束 { curr = s1.top(); s1.pop(); s2.push(curr); if(curr->lchild) s1.push(curr->lchild); if(curr->rchild) s1.push(curr->rchild); } while(!s2.empty()) { printf("%c ", s2.top()->data); s2.pop(); } }</code></pre> <h3>4、深度优先遍历</h3> <p>深度优先遍历,也就深入的遍历,沿着每一个分支直到走到最后,然后才返回来遍历剩余的节点。二叉树不同于图,图需要标记节点是否已经访问过,因为可能会存在环,而二叉树不会出现环,所以不需要标记。那么,我们只需要一个栈空间,来压栈就好了。因为深度优先遍历,遍历了根节点后,就开始遍历左子树,所以右子树肯定最后遍历。我们利用栈的性质,先将右子树压栈,然后在对左子树压栈。此时,左子树节点是在top上的,所以可以先去遍历左子树。</p> <pre> <code class="language-cpp">void DepthFirstTravel(BTree T) { stack<BTree> s; s.push(T); while(!s.empty()) { T = s.top(); cout << T->data << " "; s.pop(); if(T->rchild != NULL) { s.push(T->rchild); } if(T->lchild != NULL) { s.push(T->lchild); } } }</code></pre> <h3>5、广度优先遍历</h3> <p>对于广度优先遍历二叉树,也就是按层次的去遍历。依次遍历根节点,然后是左孩子和右孩子。所以要遍历完当前节点的所有孩子,这样才是层次遍历嘛。此时我们就不能用栈这个数据结构了,因为栈只能在栈顶操作。在这里,我们需要根据左右孩子的顺序来输出,所以就是先进先出的原则,那么我们当然就想到了队列这个数据结构。可以在rear依次插入左右孩子,在front依次读取并删除左右孩子,这样就保证了层次的输出。</p> <pre> <code class="language-cpp">void BreadthFirstTravel(BTree T) { queue<BTree> q; q.push(T); while(!q.empty()) { T = q.front(); cout << T->data << " "; q.pop(); if(T->lchild != NULL) { q.push(T->lchild); } if(T->rchild != NULL) { q.push(T->rchild); } } }</code></pre> <h2>参考</h2> <p>【1】 <a href="/misc/goto?guid=4959737443832084374" rel="nofollow,noindex">遍历二叉树的各种操作~~(非递归遍历)</a></p> <p> </p> <p>来自:http://blog.csdn.net/yixianfeng41/article/details/55228458</p> <p> </p>