二叉树前序、中序跟后序的遍历方法(递归、用栈和使用线索化)
二叉树前序、中序和后序的遍历方法(递归、用栈和使用线索化)
中序遍历实现原理:对于任一节点P,
后序遍历实现原理:对于任一节点P,
——————————————————
0、二叉树数据结构定义
struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x): val(x), left(NULL), right(NULL) {} };
1、二叉树遍历的递归法
二叉树遍历的递归法比较简单,这里直接贴出代码。
先序遍历——按照“根节点-左孩子-右孩子”的顺序进行访问。
vector<int> pre_Traversal_Recursive(TreeNode* root) { vector<int> result;//存放遍历之后结点的值 pre_Traversal_Recursive_core(root, result); return result; } void pre_Traversal_Recursive_core(TreeNode* node, vector<int> &result) { if (!node) return; result.push_back(node->val); pre_Traversal_Recursive_core(node->left, result); pre_Traversal_Recursive_core(node->right, result); }这里是根据LeetCode上的要求写的,因为需要一个vector容器返回遍历的结果,所以这里使用了两个函数实现。
中序遍历——按照“左孩子-根节点-右孩子”的顺序进行访问。
vector<int> in_Traversal_Recursive(TreeNode* root) { vector<int> result;//存放遍历之后结点的值 pre_Traversal_Recursive_core(root, result); return result; } void in_Traversal_Recursive_core(TreeNode* node, vector<int> &result) { if (!node) return; pre_Traversal_Recursive_core(node->left, result); result.push_back(node->val); pre_Traversal_Recursive_core(node->right, result); }后序遍历——按照“左孩子-右孩子-根节点”的顺序进行访问。
vector<int> post_Traversal_Recursive(TreeNode* root) { vector<int> result;//存放遍历之后结点的值 pre_Traversal_Recursive_core(root, result); return result; } void post_Traversal_Recursive_core(TreeNode* node, vector<int> &result) { if (!node) return; pre_Traversal_Recursive_core(node->left, result); pre_Traversal_Recursive_core(node->right, result); result.push_back(node->val); }
2、用栈来实现二叉树的非递归遍历
先序遍历的非递归实现
先序遍历原理:对于任一结点P,
先序遍历的非递归代码实现1)输出节点P,然后将其入栈,再看P的左孩子是否为空;2)若P的左孩子不为空,则置P的左孩子为当前节点,重复1)的操作;3)若P的左孩子为空,则将栈顶节点出栈,但不输出,并将出栈节点的右孩子置为当前节点,看其是否为空;4)若不为空,则循环至1)操作;5)如果为空,则继续出栈,但不输出,同时将出栈节点的右孩子置为当前节点,看其是否为空,重复4)和5)操作;6)直到当前节点P为NULL并且栈空,遍历结束。
vector<int> pre_Traversal_Stack(TreeNode* root) { stack<BTree> s;//用于存放树结点指针的栈 TreeNode* pCur = root;//定义访问当前结点的指针 vector<int> result;//存放遍历之后结点的值 //直到当前结点pCur为NULL且栈为空时,循环结束 while(pCur || !s.empty()) { //从根结点开始,输出当前结点,并将其入栈 //同时置其左孩子为当前结点,直至其没有左孩子,即当前结点为NULL result.push_back(pCur->val);//push_back函数会在容器尾部创建一个新元素,并使容器的长度加1 s.push(pCur); pCur = pCur->left; //如果当前结点pCur为NULL且栈不空,则将栈顶结点出栈, //同时置其右孩子为当前结点,循环判断,直至pCur不为空 while(!pCur && !s.empty()) { pCur = s.top(); s.pop(); pCur = pCur->right; } } return result; }中序遍历的非递归实现
中序遍历实现原理:对于任一节点P,
1)若P的左孩子不为空,则将P入栈并将P的左孩子置为当前节点,然后再对当前节点进行相同的处理;
2)若P的左孩子为空,则输出P节点,而后将P的右孩子置为当前节点,看其是否为空;
3)若不为空,则重复1)和2)的操作;
4)若为空,则执行出栈操作,输出栈顶节点,并将出栈的节点的右孩子置为当前节点,看其是否为空,重复3)和4)的操作;
5)直到当前节点P为NULL并且栈为空,则遍历结束。
中序遍历非递归代码实现
vector<int> in_Traversal_Stack(TreeNode *root) { TreeNode* pCur = root; stack<TreeNode*> s; vector<int> result; while(pCur || !s.empty()) { if(pCur->left == NULL) { result.push_back(pCur->val); pCur = pCur->right; } else { s.push(pCur); pCur = pCur->left; } //如果当前结点pCur为NULL且栈不空,则将栈顶结点出栈, //同时置其右孩子为当前结点,循环判断,直至pCur不为空 while(!pCur && !s.empty()) { pCur = s.top(); s.pop(); result.push_back(pCur->val); pCur = pCur->right; } } return result; }后序遍历的非递归实现
后序遍历实现原理:对于任一节点P,
1)先将节点P入栈;2)若P不存在左孩子和右孩子,或者P存在左孩子或右孩子,但左右孩子已经被输出,则可以直接输出节点P,并将其出栈,将出栈节点P标记为上一个输出的节点,再将此时的栈顶结点设为当前节点;3)若不满足2)中的条件,则将P的右孩子和左孩子依次入栈,当前节点重新置为栈顶结点,之后重复操作2);4)直到栈空,遍历结束。
后序遍历非递归代码实现
vector<int> post_Traversal_Stack(TreeNode* root) { TreeNode* pCur = root;//记录当前正在处理的结点 TreeNode* pre_Visited = NULL;//记录上一次已访问的结点 stack<TreeNode*> s;//用于遍历 vector<int> result;//记录遍历结果 if(!pCur)//根结点为空,直接返回 return result; s.push(pCur); while(!s.empty()) { pCur = s.top(); //case 1:当前结点的左右孩子都为空 //case 2:或者当前结点的左孩子或右孩子已经被访问 //则访问当前结点 if(pCur->left == NULL && pCur->right == NULL || pre_Visited && (pCur->left == pre_Visited || pCur->right == pre_Visited)) { result.push_back(pCur->val); s.pop(); pre_Visited = pCur; } else//如果条件不满足,则依次将其右孩子和左孩子入栈 { if(pCur->right) s.push(pCur->right); if(pCur->left) s.push(pCur->left); } } return result; }
3、利用线索化实现二叉树的遍历(空间复杂度为o(1))
先序遍历
先序遍历实现步骤:
1)如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。2)如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。输出当前节点(在这里输出,这是与中序遍历唯一一点不同)。当前节点更新为当前节点的左孩子。b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。当前节点更新为当前节点的右孩子。
3)重复以上1、2直到当前节点为空。
线索化实现二叉树先序遍历的代码实现:
vector<int> pre_Traversal(TreeNode* root) { TreeNode* pCur = root; TreeNode* pre;//用于找到当前结点中序遍历的直接前驱 vector<int> result;//存放遍历结果 while(pCur) { if(pCur->left == NULL)//case 1 { result.push_back(pCur->val); pCur = pCur->right; } else { pre = pCur->left;//找左子树最右结点 while(pre->right != NULL && pre->right != pCur) pre = pre->right; if(pre->right == NULL)//case 2a:构成线索二叉树 { pre->right = pCur; result.push_back(pCur->val); pCur = pCur->left; } else//case 2b:当前结点左子树遍历完成,断开线索 { pre->right = NULL; pCur = pCur->right; } } } return result; }中序遍历
中序遍历实现步骤(和先序遍历操作步骤几乎一样,唯一差别就在输出结点的时机):
1)如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。2)如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。
3)重复以上1、2直到当前节点为空。
线索化实现二叉树中序遍历的代码实现:
vector<int> in_Traversal(TreeNode *root) { TreeNode* pCur = root; TreeNode* pre;//用于找到当前结点中序遍历的直接前驱 vector<int> result;//存放遍历结果 while(pCur) { if(pCur->left == NULL)//case 1 { result.push_back(pCur->val); pCur = pCur->right; } else { pre = pCur->left;//找左子树最右结点 while(pre->right != NULL && pre->right != pCur) pre = pre->right; if(pre->right == NULL)//case 2a:构成线索二叉树 { pre->right = pCur; pCur = pCur->left; } else//case 2b:当前结点左子树遍历完成,再遍历当前结点,断开线索 { result.push_back(pCur->val); pre->right = NULL;//前序和中序不同之处就在遍历结点的时机 pCur = pCur->right; } } } return result; }后序遍历
后序遍历实现步骤:
首先新创建一个临时结点dump,并令其左孩子指向根节点,右孩子指向NULL。1)如果当前节点的左孩子为空,则将其右孩子作为当前节点。2)如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。倒序输出从当前节点的左孩子到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右孩子。
3)重复以上1、2直到当前节点为空。
线索化实现二叉树后序遍历的代码实现:
vector<int> post_Traversal(TreeNode* root) { TreeNode* dump = new TreeNode(-1); dump->left = root; TreeNode* pCur = dump; TreeNode* pre = NULL;//保存当前结点中序遍历的直接前驱结点 vector<int> result;//保存遍历结果 while(pCur) { //case 1:当前结点的左孩子为NULL,直接令当前结点 //的右孩子为当前结点 if(!pCur->left) pCur = pCur->right; else { pre = pCur->left; while(pre->right && (pre->right != pCur)) pre = pre->right; if(!pre->right)//case 2a { pre->right = pCur; pCur = pCur->left; } else//case 2b { printReverse(pCur->left, pre, result); pre->right = NULL; pCur = pCur->right; } } } dump->left = NULL;//删除辅助结点 delete(dump); dump = NULL;//消除野指针 return result; } void printReverse(TreeNode* from, TreeNode* to, vector<int> &res) { reverse(from, to); TreeNode* p = to; while(1) { res.push_back(p->val); if(p == from) break; p = p->right; } reverse(to, from); } void reverse(TreeNode* from, TreeNode* to) {//将from到to的结点的右孩子指针逆转 if(from == to) return; TreeNode* x = from, *y = from->right, *z; while(x != to) { z = y->right; y->right = x; x = y; y = z; } }
——————————————————
参考博客
http://comsci.liu.edu/~murali/algo/Morris.htm
http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/
http://blog.****.net/ns_code/article/details/12977901
http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html