一、前序遍历非递归
题目描述:写出二叉树前序遍历的非递归形式。
链接:前序遍历
思路:任何一颗树都可以认为分为左路节点,左路节点的右子树。先访问左路节点,再来访问左路节点的右子树。
先定义栈s存放节点,vector v来存放结点的数值,TreeNode* cur,cur初始化为root。
当cur不为空或者栈不为空的时候(一开始栈是空的,cur不为空),循环继续:先把左路节点存放进栈中,同时把值存入v中,一直循环,直到此时的左路节点为空,访问结束。在弹出栈顶元素top,把top->right赋值给我们的cur,就可以转化成子问题去访问左路节点的右子树了。
~ 栈s不为空说明此时还有左路节点的右子树还没访问,cur不为空说明此时还有树要去访问。当两个同时为空时,循环结束,最终得到前序遍历。
~ 一个节点出栈说明这个节点及其左子树已经访问完了,因为我们是先把左路节点存入栈中,此时还剩右子树没有访问。
代码实现:
class Solution { public: vector<int> preorderTraversal(TreeNode* root) { stack<TreeNode*> s; vector<int> v; TreeNode* cur = root; while(cur || !s.empty()) { while(cur) { s.push(cur); v.push_back(cur->val); cur = cur->left; } TreeNode* top = s.top(); s.pop(); cur = top->right; } return v; } };
二、中序遍历非递归
题目描述:写出二叉树中序遍历的非递归形式。
链接:中序遍历
思路:中序遍历是左、根、右。左子树访问完之后才能去访问根。左路节点一直走直到左子树访问完,入栈的过程中不去进行访问(存放数值到v中),当左路节点出栈之后,也就是从栈中弹出进行访问。
代码实现:
class Solution { public: vector<int> inorderTraversal(TreeNode* root) { vector<int> v; stack<TreeNode*> s; TreeNode* cur = root; while(cur || !s.empty()) { while(cur) { s.push(cur); cur = cur->left; } TreeNode* top = s.top(); v.push_back(top->val); s.pop(); cur = top->right; } return v; } };
三、后序遍历非递归
题目描述:写出二叉树后序遍历的非递归形式。
链接:后序遍历
思路:我们定义一个栈,在栈里面取到一个节点时:右子树是否访问过,如果没有访问,迭代子问题访问,如果访问过了,则访问这个根节点,pop出栈。
如果top的右子树为空或者右子树已经访问过了(上一个访问节点是右子树的根),那么说明右子树不用访问或者访问过了,可以访问根top;当右子树不为空,且没有访问过,则迭代子问题访问。
通过prev来判断上一次访问的节点:如果prev等于top->right时,表示栈顶节点的右子树已经访问过了,可以弹出栈顶节点并访问它。
代码实现:
class Solution { public: vector<int> postorderTraversal(TreeNode* root) { stack<TreeNode*> s; vector<int> v; TreeNode* cur = root; TreeNode* prev = nullptr; while(cur || !s.empty()) { while(cur) { s.push(cur); cur = cur->left; } TreeNode* top = s.top(); if(top->right == nullptr || top->right == prev) { v.push_back(top->val); prev = top; s.pop(); } else { cur = top->right; } } return v; } };
四、二叉树转链表
题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。
注意:
1、要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2、返回链表中的第一个节点的指针
3、函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
4、你不用输出双向链表,程序会根据你的返回值自动打印输出
链接:二叉树转链表
代码实现:
class Solution { public: void InorderConvert(TreeNode* cur, TreeNode*& prev) { if(cur == nullptr) return; InorderConvert(cur->left, prev); cur->left = prev; if(prev) prev->right = cur; prev = cur; InorderConvert(cur->right, prev); } TreeNode* Convert(TreeNode* pRootOfTree) { TreeNode* prev = nullptr; InorderConvert(pRootOfTree, prev); TreeNode* head = pRootOfTree; while(head && head->left) { head = head->left; } return head; } };
五、二叉树的最近公共祖先
题目描述:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
链接:二叉树的公共祖先
方法一:
思路:把根到对应节点的路径存储起来,在找出相交的结点即是最近的公共结点。
代码实现:
class Solution { public: bool FindPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path) { if(root == nullptr) return false; path.push(root); if(root == x) return true; if(FindPath(root->left, x, path)) return true; if(FindPath(root->right, x, path)) return true; path.pop(); return false; } TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { stack<TreeNode*> pPath, qPath; FindPath(root, p, pPath); FindPath(root, q, qPath); while(pPath.size() != qPath.size()) { if(pPath.size() > qPath.size()) pPath.pop(); else qPath.pop(); } while(pPath.top() != qPath.top()) { pPath.pop(); qPath.pop(); } return qPath.top(); } };
方法二:
思路:我们先分析下题干,可以知道:要找两个节点的公共祖先,我们通过规律发现这两个节点一定是分别在它们的公共祖先的左子树和右子树。
那么有了这个规律,我们就可以这样去找:当遍历到一个节点时,如果p在它的右子树且q在它的左子树或者p在左子树且q在右子树,那么它就是公共祖先。如p,q在同一侧,那么该节点一定不是p,q的公共祖先,继续递归寻找,如果p,q在左子树,就到左子树中找,想反的就去右子树中找。
代码实现:
class Solution { public: bool Find(TreeNode* root, TreeNode* x) { if(root == nullptr) return false; if(root == x) return true; return Find(root->left, x) || Find(root->right, x); } TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if(root == nullptr) return nullptr; if(root == p || root == q) return root; bool pInleft, pInright, qInleft, qInright; pInleft = Find(root->left, p); pInright = !pInleft; qInleft = Find(root->left, q); qInright = !qInleft; if((pInleft && qInright) || (pInright && qInleft)) return root; else if(pInleft && qInleft) return lowestCommonAncestor(root->left, p, q); else if(pInright && qInright) return lowestCommonAncestor(root->right, p, q); else return nullptr; } };
六、二叉树的层序遍历1
题目描述:给你二叉树的根节点 root ,返回其节点值的 层序遍历。
链接:二叉树的层序遍历
思路:既然是层序遍历,根据我们之前的经验,我们可以使用队列+循环的方式进行控制。但是,我们需要把每一层的数放在同一个数组中(不同层的数要在不同的数组中)。那么我们应该怎么来表示当前层的数已经全部进入同一个数组了呢?
以下面的图为例。首先,我们要明确的是:第一层只有一个节点,所以第一个数组只有一个数,所以只循环一次;在第一层的节点出队列后,第二层的节点已经进入队列,此时队列的元素个数就是第二层的元素个数,而第二层有两个节点,需要循环两次。
这里我们可以定义一个变量 LevelSize,来帮助我们记录每一层的节点个数。根据上面的规律,我们知道:每一层要循环节点个数次,当LevelSize为0时,表示该层已经遍历完,然后更新LevelSize的大小为队列元素个数。
代码实现:
class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { queue<TreeNode*> q; vector<vector<int>> vv; int LeverSize = 1; if(root != nullptr) { q.push(root); } while(!q.empty()) { vector<int> v; while(LeverSize--) { TreeNode* cur = q.front(); if(cur->left != nullptr) q.push(cur->left); if(cur->right != nullptr) q.push(cur->right); q.pop(); v.push_back(cur->val); } vv.push_back(v); LeverSize = q.size(); } return vv; } };
七、二叉树的层序遍历2
题目描述:给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)。
链接:二叉树的层序遍历2
思路:最简单的方法就是使用题目六中的遍历方法,最后用逆置算法将数组逆置即可。
代码实现:
class Solution { public: vector<vector<int>> levelOrderBottom(TreeNode* root) { queue<TreeNode*> q; vector<vector<int>> vv; int LeverSize = 1; if(root != nullptr) { q.push(root); } while(!q.empty()) { vector<int> v; while(LeverSize--) { TreeNode* cur = q.front(); if(cur->left != nullptr) q.push(cur->left); if(cur->right != nullptr) q.push(cur->right); q.pop(); v.push_back(cur->val); } vv.push_back(v); LeverSize = q.size(); } reverse(vv.begin(), vv.end()); return vv; } };