赞
踩
树本身是一种简单化的图 ;
DFS对应前中后序遍历,BFS对应层序遍历
struct treenode {
int val;
treenode *left;
treenode *right;
treenode() : val(0), left(nullptr), right(nullptr) {}
treenode(int x) : val(x), left(nullptr), right(nullptr) {}
treenode(int x, treenode *left, treenode *right) : val(x), left(left), right(right) {}
};
二叉树的中序遍历就是首先遍历左子树,然后访问当前节点,最后遍历右子树。对于下面的二叉树,中序遍历结果如下:
直观来看,二叉树的中序遍历就是将节点投影到一条水平的坐标上。如图:
这是思路最简单的方法,容易想到并且容易实现。递归的终止条件是当前节点是否为空。首先递归调用遍历左子树,然后访问当前节点,最后递归调用右子树。代码如下:
//recursive class Solution1 { public: vector<int> inorderTraversal(TreeNode* root) { vector<int> ret; if(root==NULL)return ret; inorderHelper(ret,root); return ret; } private: void inorderHelper(vector<int>& ret,TreeNode* root) { if(root==NULL)return; inorderHelper(ret,root->left); ret.push_back(root->val); inorderHelper(ret,root->right); } };
在迭代方法中,从根节点开始找二叉树的最左节点,将走过的节点保存在一个栈中,找到最左节点后访问,对于每个节点来说,它都是以自己为根的子树的根节点,访问完之后就可以转到右儿子上了。代码如下:
class Solution { public: vector<int> inorderTraversal(TreeNode* root) { vector<int> res; stack<TreeNode*> stk; while (root != nullptr || !stk.empty()) { while (root != nullptr) { stk.push(root); //要在循环之后执行push(root) root = root->left; } root = stk.top(); stk.pop(); res.push_back(root->val); root = root->right; } return res; } };
时间复杂度和空间复杂度同递归。
非递归的空间复杂度和二叉树的深度和高度有关
Morris 遍历算法是另一种遍历二叉树的方法,它能将非递归的中序遍历空间复杂度降为 O(1)。
Morris 遍历算法整体步骤如下(假设当前遍历到的节点为 x):
如果 x 无左孩子,先将 x 的值加入答案数组,再访问 x 的右孩子,即 x=x.right。
如果 x 有左孩子,则找到 x 左子树上最右的节点(即左子树中序遍历的最后一个节点,x 在中序遍历中的前驱节点),我们记为 predecessor。根据 predecessor 的右孩子是否为空,进行如下操作。
如果predecessor 的右孩子为空,则将其右孩子指向 x,然后访问 x 的左孩子,即 x=x.left。
如果predecessor 的右孩子不为空,则此时其右孩子指向 x,说明我们已经遍历完 x 的左子树,我们将predecessor 的右孩子置空,将 x 的值加入答案数组,然后访问 x 的右孩子,即 x=x.right。
重复上述操作,直至访问完整棵树。
class Solution { public: vector<int> inorderTraversal(TreeNode* root) { vector<int> res; TreeNode *predecessor = nullptr; while (root != nullptr) { if (root->left != nullptr) { // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止 predecessor = root->left; while (predecessor->right != nullptr && predecessor->right != root) { predecessor = predecessor->right; } // 让 predecessor 的右指针指向 root,继续遍历左子树 if (predecessor->right == nullptr) { predecessor->right = root; root = root->left; } // 说明左子树已经访问完了,我们需要断开链接 else { res.push_back(root->val); predecessor->right = nullptr; root = root->right; } } // 如果没有左孩子,则直接访问右孩子 else { res.push_back(root->val); root = root->right; } } return res; } };
调整下访问顺序即可。代码如下:
//recursion class Solution1 { public: vector<int> preorderTraversal(TreeNode* root) { vector<int> ret; preHelper(ret,root); return ret; } private: void preHelper(vector<int>& ret,TreeNode* root) { if(root==NULL)return; ret.push_back(root->val); preHelper(ret,root->left); preHelper(ret,root->right); } };
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
l=[]
def dfs(root:Optional[TreeNode])->List[int]:
if not root:
return None
l.append(root.val)
dfs(root.left)
dfs(root.right)
return l
dfs(root)
return l
迭代法使用一个栈来保存当前不需要访问的节点。从根节点开始,访问当前节点,按照先右儿子后左儿子的顺序将当前节点的两个儿子压栈。当栈为空时说明遍历完毕。代码如下:
//iterative class Solution2 { public: vector<int> preorderTraversal(TreeNode* root) { vector<int> ret; stack<TreeNode*> st; if(root==NULL)return ret; st.push(root); while(!st.empty()) { TreeNode *curr=st.top(); st.pop(); if(curr->right)st.push(curr->right); if(curr->left)st.push(curr->left); ret.push_back(curr->val); } return ret; } };
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res=list()
stk=[]
if not root:
return res
stk.append(root)
while stk:
node=stk.pop()
if node.right:
stk.append(node.right)
if node.left:
stk.append(node.left)
res.append(node.val)
return res
前序遍历和中序遍历的Morris方法基本一样,不同之处在访问的顺序。代码如下:
//Morris class Solution3 { public: vector<int> preorderTraversal(TreeNode* root) { vector<int> ret; if(root==NULL)return ret; TreeNode *curr=root; TreeNode *pre; while(curr) { if(curr->left==NULL) { ret.push_back(curr->val); curr=curr->right; } else { pre=curr->left; while(pre->right&&pre->right!=curr) pre=pre->right; if(pre->right==NULL) { ret.push_back(curr->val); pre->right=curr; curr=curr->left; } else { pre->right=NULL; curr=curr->right; } } } return ret; } };
class Solution: def preorderTraversal(self, root: TreeNode) -> List[int]: res = list() if not root: return res p1 = root while p1: p2 = p1.left if p2: while p2.right and p2.right != p1: p2 = p2.right if not p2.right: res.append(p1.val) p2.right = p1 p1 = p1.left continue else: p2.right = None else: res.append(p1.val) p1 = p1.right return res
二叉树的层序遍历:一般基于队列的实现
首先将二叉树的根节点push到队列中。
判断队列不为空就输出队头元素。
判断当前对头节点是否有孩子节点,有则push到队列中。
循环操作,直到队列为空。
vector<int> levelOrder(TreeNode* Tree) //层序遍历_队列实现 { vector<int>res; queue < TreeNode* > q; if (Tree != NULL) q.push(Tree); //根节点进队列 while (q.empty() == false) //队列不为空 { TreeNode* node=q.front(); res.push_back(node->val); if (node->left != NULL) //如果有左孩子,入队 q.push(node->left); if (node->right != NULL) //如果有右孩子,入队 q.push(node->right); q.pop(); //已经遍历过的节点出队列 } return res; }
class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> res; vector<int> level_data; if(root==NULL) return res; queue<TreeNode*> q; q.push(root); q.push(NULL); while(!q.empty()) { TreeNode* temp=q.front(); q.pop(); if(temp) { level_data.push_back(temp->val); if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); } else { if(!level_data.empty()) { q.push(NULL); res.push_back(level_data); level_data.clear(); } } } return res; } };
可以归纳出这样的循环不变式:第 i 次迭代前,队列中的所有元素就是第 i 层的所有元素,并且按照从左向右的顺序排列。证明它的三条性质(你也可以把它理解成数学归纳法):
初始化:i=1 的时候,队列里面只有 root,是唯一的层数为 1 的元素,因为只有一个元素,所以也显然满足「从左向右排列」;
保持:如果 i=k 时性质成立,即第 k 轮中出队sk 的元素是第 k层的所有元素,并且顺序从左到右。因为对树进行广度优先搜索的时候由低 k 层的点拓展出的点一定也只能是 k+1 层的点,并且 k+1 层的点只能由第 k 层的点拓展到,所以由这 sk 个点能拓展到下一层所有的 sk+1 个点。又因为队列的先进先出(FIFO)特性,既然第 k 层的点的出队顺序是从左向右,那么第 k+1层也一定是从左向右。至此,我们已经可以通过数学归纳法证明循环不变式的正确性。
终止:因为该循环不变式是正确的,所以按照这个方法迭代之后每次迭代得到的也就是当前层的层次遍历结果。至此,我们证明了算法是正确的。
class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { vector <vector <int>> ret; if (!root) { return ret; } queue <TreeNode*> q; q.push(root); while (!q.empty()) { int currentLevelSize = q.size(); ret.push_back(vector <int> ()); for (int i = 1; i <= currentLevelSize; ++i) { auto node = q.front(); q.pop(); ret.back().push_back(node->val); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } } return ret; } };
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
class Solution { public: vector<vector<int>> levelOrderBottom(TreeNode* root) { vector<vector<int>>ret; if(!root) return ret; queue<TreeNode*>q; q.push(root); while(!q.empty()) { int currentLevelSize=q.size(); ret.push_back(vector<int>()); for(int i=1;i<=currentLevelSize;++i) { auto node=q.front();q.pop(); ret.back().push_back(node->val); if(node->left)q.push(node->left); if(node->right)q.push(node->right); } } reverse(ret.begin(),ret.end()); return ret; } };
利用两个栈
//按之字形打印 void Print(TreeNode *root) { if (root == NULL) return; stack<TreeNode*>levels[2]; int current = 0, next = 1; levels[current].push(root); while (!levels[0].empty() || !levels[1].empty()) { TreeNode*node = levels[current].top(); levels[current].pop(); printf("%d", node->val); if (current == 0) { if (node->left != nullptr) levels[next].push(node->left); if (node->right != nullptr) levels[next].push(node->right); } else { if (node->right != nullptr) levels[next].push(node->right); if (node->left != nullptr) levels[next].push(node->left); } if (levels[current].empty()) { cout << endl; current = 1 - current; next = 1 - next; } } }
class Solution { public: vector<vector<int>> zigzagLevelOrder(TreeNode* root) { vector<vector<int>>vec; if(root==NULL) return vec; stack<TreeNode*>levels[2]; int current=0,next=1; levels[current].push(root); vec.push_back(vector<int>()); while(!levels[current].empty()||!levels[next].empty()) { TreeNode *pNode=levels[current].top(); levels[current].pop(); if(pNode==NULL) continue; vec.back().push_back(pNode->val); if(current==0) { if(pNode->left) levels[next].push(pNode->left); if(pNode->right) levels[next].push(pNode->right); } else { if(pNode->right) levels[next].push(pNode->right); if(pNode->left) levels[next].push(pNode->left); } if(levels[current].empty()) { vec.push_back(vector<int>()); current=1-current; next=1-next; } } vec.pop_back(); return vec; } };
//recursion class Solution1 { public: vector<int> postorderTraversal(TreeNode* root) { vector<int> ret; postHelper(ret,root); return ret; } private: void postHelper(vector<int>& ret,TreeNode* root) { if(root==NULL)return; postHelper(ret,root->left); postHelper(ret,root->right); ret.push_back(root->val); } };
迭代法使用一个栈来保存当前不需要访问的节点。不过,不同于中序遍历与前序遍历,在后序遍历中每一个节点需要一个标志位,来标识当前节点的左右子树是否被访问。因为在后序遍历中,只有一个节点的左右子树被访问后它才能被访问。因此,压入栈中的数据类型需要是一个pair<TreeNode*,int>,其中用1来表示当前节点的左右子树正被访问,当再次访问到此节点时可以访问此节点;用0表示当前节点的左右子树未被访问,再次访问到此节点时需要首先访问此节点的左右子树。
//iteration class Solution1 { public: vector<int> postorderTraversal(TreeNode* root) { vector<int> ret; if(root==NULL)return ret; stack<pair<TreeNode*,int>> st; st.push(make_pair(root,0)); while(!st.empty()) { TreeNode *curr=st.top().first; if(st.top().second==1) { ret.push_back(curr->val); st.pop(); } else { st.top().second=1; if(curr->right)st.push(make_pair(curr->right,0)); if(curr->left)st.push(make_pair(curr->left,0)); } } return ret; } };
这种方法就是按照根、右、左的顺序访问,然后将结果取反即可。后序遍历的顺序是左、右、根。这种方法就可以在前序遍历的基础上修改即可。代码如下:
class Solution3 { public: vector<int> postorderTraversal(TreeNode* root) { vector<int> ret; if(root==NULL)return ret; stack<TreeNode*> st; st.push(root); while(!st.empty()) { TreeNode *curr=st.top(); st.pop(); if(curr->left)st.push(curr->left); if(curr->right)st.push(curr->right); ret.push_back(curr->val); } reverse(ret.begin(),ret.end()); return ret; } };
后序遍历的Morris方法思路比较难。但整体上还是一样的,对原来的二叉树的修改也是一样的,不同的是访问的顺序。而在后序遍历中,访问时比较麻烦。下面是整个算法的工作过程;
首先建立一个临时节点dump,令其左儿子是root。并且还需要一个子过程,就是倒序输出某两个节点之间路径上的各个节点。
步骤:
当前节点设置为临时节点dump。
(1)如果当前节点的左儿子为空,则将其右儿子作为当前节点;
(2)如果当前节点的左儿子非空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;
a) 如果前驱节点的右孩子为空,将它的右儿子设置为当前节点。当前节点更新为当前节点的左儿子;
b) 如果前驱节点的右儿子为当前节点,将它的右孩子重新设为空。倒序输出从当前节点的左儿子到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右儿子;
(3)重复以上(1)(2)直到当前节点为空。
代码如下:
//morris class Solution4 { public: vector<int> postorderTraversal(TreeNode* root) { vector<int> ret; TreeNode *dump=new TreeNode(0); dump->left=root; TreeNode *curr=dump; TreeNode *pre; while(curr) { if(curr->left==NULL) { curr=curr->right; } else { pre=curr->left; while(pre->right&&pre->right!=curr) pre=pre->right; if(pre->right==NULL) { pre->right=curr; curr=curr->left; } else { reverseAddNodes(curr->left,pre,ret); pre->right=NULL; curr=curr->right; } } } return ret; } private: void reverseAddNodes(TreeNode *begin,TreeNode *end,vector<int>& ret) { reverseNodes(begin,end); TreeNode *curr=end; while(true) { ret.push_back(curr->val); if(curr==begin)break; curr=curr->right; } reverseNodes(end,begin); } void reverseNodes(TreeNode *begin,TreeNode *end) { TreeNode *pre=begin; TreeNode *curr=pre->right; TreeNode *post; while(pre!=end) { post=curr->right; curr->right=pre; pre=curr; curr=post; } } };
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。