[数据结构]二叉树的遍历及构造树
2016-05-09 23:19
323 查看
二叉树的标准遍历方式有三种,分别为先序遍历、中序遍历和后序遍历,本篇文章主要介绍这三种遍历方式的非递归实现以及通过三种遍历方式中的两种(先序遍历和后序遍历除外)构造出结构化的树。
数据结构如下:
一、先序遍历:
先序遍历的访问顺序是 root->left->right,可以借用一个栈来实现,对于任意一棵子树,当访问到其根节点时,记为curr,将curr的val放入ans数组中表示已访问过,并且将其出栈。根据栈的性质,需要将right先入栈left后入栈,这样在出栈的时候就能保证先序遍历的顺序了。
二、中序遍历:
中序遍历的顺序为left->root->right,在遍历过程中,由于访问到的根节点不能直接出栈,如果出栈以后就找不到了,而不出栈的话会涉及到重复访问。
一开始沿着树根的left一直向下走,找到最左叶子节点,并把该路径上的所有节点都记录下来。
之后每次访问栈顶的元素并将其出栈,如果该节点含有右子树,则在该节点出栈后将它的右子树的以根节点的处理方式将其最左子节点入栈。
三、后序遍历:
后序遍历的顺序为left->right->root,在遍历过程中,由于访问到的根节点还不能出栈,如果出栈了以后就找不到了,这里通过将一个子树的根节点的左孩子和右孩子标记为NULL的方式来帮助进行后序遍历,但会影响树的结构。当然也可以通过开辟额外空间记录状态来维持树结构。
上述方法会影响到树的结构,不过还有一种不影响树本身结构的方法。
回忆先序遍历和后序遍历两种方法,只是在访问根节点的先后顺序存在差异,故可以借用先序遍历的方法,先访问根节点,然后访问右子树,最后访问左子树,这样得到的遍历结果反转一下即为后序遍历的结果。
通过先序遍历和中序遍历、中序遍历和后序遍历递归构造树的方式比较简单,这里将着重介绍非递归的方式。
四、根据先序遍历和中序遍历构造树:
每次判断栈顶元素是否与当前要访问的中序遍历对应位置pos元素相等,如果不相等,说明要继续构造左子树;否则,说明当前栈顶元素已经是相对最左子树了,此时可将其出栈,同时移动中序遍历的指针pos,继续匹配是否相等,直到inorder[pos]与当前栈顶元素不等,说明它在当前栈顶元素的右子树中,而右子树则正是要以preorder[i]建立的,所以将该右子树根节点压栈。
五、根据中序遍历和后序遍历构造树:
与先序遍历类似,但对postorder和inorder的访问从末尾开始,同样用pos记录中序遍历的访问指针。
先将postorder的最后一个元素压栈,即根元素,之后每次判断栈顶元素是否与中序遍历指针所指元素相等,如果不是,则继续拓展右子树。否则,说明当前栈顶元素即为此时将要生成的最右子树,将其出栈并前移中序遍历的指针,连续出栈直到当前栈顶元素与中序遍历访问点值不等,说明此时中序遍历访问的点在左子树中,此左子树即以postorder[i]为根建立的,故将其压栈。
数据结构如下:
struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} };
一、先序遍历:
先序遍历的访问顺序是 root->left->right,可以借用一个栈来实现,对于任意一棵子树,当访问到其根节点时,记为curr,将curr的val放入ans数组中表示已访问过,并且将其出栈。根据栈的性质,需要将right先入栈left后入栈,这样在出栈的时候就能保证先序遍历的顺序了。
vector<int> preorderTraversal(TreeNode* root) { vector<int> ans; if (root == NULL) return ans; stack<TreeNode*> treeStack; treeStack.push(root); while(!treeStack.empty()) { TreeNode *curr = treeStack.top(); treeStack.pop(); ans.push_back(curr->val); if (curr->right) { treeStack.push(curr->right); } if (curr->left) { treeStack.push(curr->left); } } return ans; }
二、中序遍历:
中序遍历的顺序为left->root->right,在遍历过程中,由于访问到的根节点不能直接出栈,如果出栈以后就找不到了,而不出栈的话会涉及到重复访问。
一开始沿着树根的left一直向下走,找到最左叶子节点,并把该路径上的所有节点都记录下来。
之后每次访问栈顶的元素并将其出栈,如果该节点含有右子树,则在该节点出栈后将它的右子树的以根节点的处理方式将其最左子节点入栈。
vector<int> inorderTraversal(TreeNode* root) { vector<int> ans; if (root == NULL) return ans; stack<TreeNode*> treeStack; TreeNode *curr=root, *tmp; while(curr) { treeStack.push(curr); curr = curr->left; } while (!treeStack.empty()) { curr = treeStack.top(); ans.push_back(curr->val); treeStack.pop(); tmp = curr->right; while (tmp) { treeStack.push(tmp); tmp = tmp->left; } } return ans; }
三、后序遍历:
后序遍历的顺序为left->right->root,在遍历过程中,由于访问到的根节点还不能出栈,如果出栈了以后就找不到了,这里通过将一个子树的根节点的左孩子和右孩子标记为NULL的方式来帮助进行后序遍历,但会影响树的结构。当然也可以通过开辟额外空间记录状态来维持树结构。
vector<int> postorderTraversal(TreeNode* root) { vector<int> ans; if (root == NULL) return ans; stack<TreeNode*> treeStack; treeStack.push(root); while(!treeStack.empty()) { TreeNode *curr = treeStack.top(); if (curr->left==NULL && curr->right==NULL) { ans.push_back(curr->val); treeStack.pop(); } if (curr->right) { treeStack.push(curr->right); curr->right = NULL; } if (curr->left) { treeStack.push(curr->left); curr->left = NULL; } } return ans; }
上述方法会影响到树的结构,不过还有一种不影响树本身结构的方法。
回忆先序遍历和后序遍历两种方法,只是在访问根节点的先后顺序存在差异,故可以借用先序遍历的方法,先访问根节点,然后访问右子树,最后访问左子树,这样得到的遍历结果反转一下即为后序遍历的结果。
vector<int> postorderTraversal(TreeNode* root) { vector<int> ans; if (root == NULL) return ans; stack<TreeNode*> treeStack; treeStack.push(root); while(!treeStack.empty()) { TreeNode *curr = treeStack.top(); treeStack.pop(); ans.push_back(curr->val); if (curr->left) { treeStack.push(curr->left); } if (curr->right) { treeStack.push(curr->right); } } reverse(ans.begin(), ans.end()); return ans; }
通过先序遍历和中序遍历、中序遍历和后序遍历递归构造树的方式比较简单,这里将着重介绍非递归的方式。
四、根据先序遍历和中序遍历构造树:
每次判断栈顶元素是否与当前要访问的中序遍历对应位置pos元素相等,如果不相等,说明要继续构造左子树;否则,说明当前栈顶元素已经是相对最左子树了,此时可将其出栈,同时移动中序遍历的指针pos,继续匹配是否相等,直到inorder[pos]与当前栈顶元素不等,说明它在当前栈顶元素的右子树中,而右子树则正是要以preorder[i]建立的,所以将该右子树根节点压栈。
TreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) { TreeNode *root=NULL; stack<TreeNode*> treeStack; if(preorder.empty()) return root; root = new TreeNode(preorder[0]); treeStack.push(root); int pos = 0; for(int i = 1; i < preorder.size(); i++) { TreeNode *curr = treeStack.top(); if(curr->val != inorder[pos]) { curr->left = new TreeNode(preorder[i]); treeStack.push(curr->left); } else { while(!treeStack.empty() && ((treeStack.top()->val)==inorder[pos])) { curr = treeStack.top(); treeStack.pop(); pos++; } curr->right = new TreeNode(preorder[i]); treeStack.push(curr->right); } } return root; }
五、根据中序遍历和后序遍历构造树:
与先序遍历类似,但对postorder和inorder的访问从末尾开始,同样用pos记录中序遍历的访问指针。
先将postorder的最后一个元素压栈,即根元素,之后每次判断栈顶元素是否与中序遍历指针所指元素相等,如果不是,则继续拓展右子树。否则,说明当前栈顶元素即为此时将要生成的最右子树,将其出栈并前移中序遍历的指针,连续出栈直到当前栈顶元素与中序遍历访问点值不等,说明此时中序遍历访问的点在左子树中,此左子树即以postorder[i]为根建立的,故将其压栈。
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { TreeNode *root = NULL; stack<TreeNode*> treeStack; if (postorder.empty()) return root; root = new TreeNode(postorder[postorder.size()-1]); treeStack.push(root); int pos = inorder.size()-1; for (int i = postorder.size()-2; i >= 0; --i) { TreeNode *curr = treeStack.top(); if (curr->val != inorder[pos]) { curr->right = new TreeNode(postorder[i]); treeStack.push(curr->right); } else { while(!treeStack.empty() && treeStack.top()->val==inorder[pos]) { curr = treeStack.top(); treeStack.pop(); pos--; } curr->left = new TreeNode(postorder[i]); treeStack.push(curr->left); } } return root; }
相关文章推荐
- 【搜索引擎】搜索引擎索引数据结构和算法
- 数据结构之Trie树
- 数据结构 笔记3 顺序表和链表
- 二维数组中查找值
- 共同学习Java源代码--数据结构--AbstractList抽象类(五)
- [数据结构]适用于字符串的Hash函数
- 哈夫曼树与哈夫曼编码
- 堆排序
- 数据结构:树状数组
- 共同学习Java源代码--数据结构--AbstractList抽象类(四)
- SDUT 3363 数据结构实验之图论七:驴友计划m
- 栈的创建等操作及使用案例(检测括号匹配)
- 数据结构与算法练习-Stack栈
- 数据结构:队列
- 数据结构--用栈求解迷宫问题(非最优解)
- 数据结构与算法练习-数组查找,排序
- 共同学习Java源代码--数据结构--AbstractList抽象类(三)
- 数据结构-队列(queue)
- MySQL索引背后的数据结构及算法原理
- Android JNI 使用的数据结构JNINativeMethod详解