二叉树遍历是二叉树的最基本的操作,其实现方式主要有: 1. 递归遍历 2. 非递归遍历 3. Morris遍历(自己度娘去,这里不讲)
递归遍历的实现非常容易,非递归实现需要用到栈。而Morris算法强大之处在于只需要使用O(1)的空间就能实现对二叉树O(n)时间的遍历。
每个二叉树结点包括一个值以及左孩子和右孩子结点,其定义如下:
//定义树 struct Tree { char data; Tree *LeftChild; Tree *RightChild; };二叉树的遍历,就是按照某条搜索路径访问树中的每一个结点,使得每个结点均被访问一次,而且仅被访问一次。常见的遍历次序有:
先序遍历:先访问根结点,再访问左子树,最后访问右子树中序遍历:先访问左子树,再访问根结点,最后访问右子树后序遍历:先访问左子树,再访问右子树,最后访问根结点递归实现非常简单,按照遍历的次序,对当前结点分别调用左子树和右子树即可。
二叉树遍历的递归实现,每个结点只需遍历一次,故时间复杂度为O(n)。而使用了递归,最差情况下递归调用的深度为O(n),所以空间复杂度为O(n)。
二叉树遍历的非递归实现,可以借助栈。
将根结点入栈; 每次从栈顶弹出一个结点,访问该结点; 把当前结点的右孩子入栈; 把当前结点的左孩子入栈。 按照以上顺序入栈,这样出栈顺序就与先序遍历一样:先根结点,再左子树,最后右子树。
void PreOrderVisit2(Tree *T) { stack<Tree *>S; Tree *p; p = T; while(!S.empty() || nullptr != p) { if(nullptr != p) { S.push(p); p = p->LeftChild; } else { p = S.top(); S.pop(); cout << p->data << ends; p = p->RightChild; } } }初始化一个二叉树结点pNode指向根结点; 若pNode非空,那么就把pNode入栈,并把pNode变为其左孩子;(直到最左边的结点) 若pNode为空,弹出栈顶的结点,并访问该结点,将pNode指向其右孩子(访问最左边的结点,并遍历其右子树)
void InOrderVisit2(Tree *T) { stack<Tree *>S; //用来保存的节点的栈 Tree *p; p = T; while(!S.empty() || nullptr != p) { if(nullptr != p) //p非空 { S.push(p); cout << p->data << ends; p = p->LeftChild; } else { p = S.top(); S.pop(); p = p->RightChild; } } }需要借助一个指针来标记已遍历过该结点。
void LastOrderVisit2(Tree *T) { stack<Tree *>S; Tree *p; Tree *pre = nullptr; p = T; while(!S.empty() || nullptr != p) { while(nullptr != p) { S.push(p); p = p->LeftChild; } p = S.top(); if(p->RightChild == NULL || p->RightChild == pre) { cout << p->data << ends; pre = p; p = nullptr; S.pop(); } else { p = p->RightChild; } } }复杂度分析
二叉树遍历的非递归实现,每个结点只需遍历一次,故时间复杂度为O(n)。而使用了栈,空间复杂度为二叉树的高度,故空间复杂度为O(n)。