**
二叉树是常见的数据结构,本期分享二叉树的升级版算法。难懂的地方本人已经做出注释以及相应的图示分析帮助大家理解。**
思路:
代码以及注释理解如下:
string tree2str(TreeNode* root)
{
string str;
if(root==nullptr)
return str;
str += to_string(root->val);
if(root->left || root->right)
//如果左不为空或者左为空右不为空,都需要有()在中间
{
str+='(';
str+= tree2str(root->left);
str+=')';
}
//能下来就说明可能是是左不为空,然后来判断右子树是不是空。
//如果不是空,就正常添加。
//如果是空,就不需要进行处理了。
if(root->right)
{
str+='(';
str+=tree2str(root->right);
str+=')';
}
return str;
}
用子函数,然后字符串后缀添加时用字符串的引用就可以减少拷贝次数。
string _tree2str(TreeNode* root,string & str)
{
if(root==nullptr)
return str;
str+=to_string(root->val);
if(root->left||root->right)
{
str+='(';
_tree2str(root->left,str);
str+=')';
}
if(root->right)
{
str+='(';
_tree2str(root->right,str);
srt+=')';
}
return str;
}
string tree2str(TreeNode* root)
{
string str;
_tree2str(root,str);
return str;
}
每一层一次性放到一个数组当中。
vector<vector<int>> levelorder(TreeNode* root)
{
queue<TreeNode*>q;
int levelsize=0;
if(root)
{
q.push(root);
levelsize=1;
}
vector<vector<int>>vv;
while(!q.empty())
{
//通过levelsize控制一层一层出
vector<int >v;
while(levelsize--)
{
TreeNode* front=q.front();
q.pop();
v.push_back(front->val);
if(front->left)
q.push(front->left);
if(front->right)
q.push(front->right);
}
//一层出完了,更新到下一层的数据。因为此时队列中都是下一层的数据
levelsize = q.size();
vv.push_back(v);
}
return vv;
}
注:(自己可以认为是自己的祖先)
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;
}
上述这种算法就是查了太多遍。还有就是很多单边子树,极端为O(N*N)。所以升级算法在下:
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 pPath.top();
}
二叉搜索树转化为排序的双向链表(二叉树的线索化)和中序遍历有关系
要求不创建任何新的节点,只能调整树中节点指针的指向,左指针充当前驱
右指针当成next。
思路演示:
void InOrder(TreeNode* cur,TreeNode* &prev)
{
if(cur==NUll)
return ;
InOrder(root->left,prev);
cur->left=prev;
if(prev)
prev->right=cur;
prev=cur;
InOrder(root->right,prev);
}
TreeNode* Convert(TreeNode* pRootOfTree)
{
if(pRootOfTree==nullptr)
return nullptr;
TreeNode* prev=nullptr;
InOrder(pRootOfTree,prev);
//找到最左节点进行返回
TreeNode* head=pRootOfTree;
while(head->left)
{
head=head->left;
}
return head;
}
通过前序和中序重建二叉树(没有重复数据的前提下)
通过前序确定根的位置,在中序区间,分割左右子树。
注:如果是后序和中序就需要调整一下顺序,posti–,先构建右子树,再构建左子树。
TreeNode* _buildTree(vector<int>& preorder,vector<int>&inorder,int &prei,int inbegin,int inend)
{
if(inbegin>inend)
return nullptr;
TreeNode* root=new TreeNode(preorder[prei]);//实现重构节点
//将中序区分出来
int rooti=inbegin;
while(rooti<=inend)//找到根节点在中序的位置实现分割
{
if(inorder[rooti]==preorder[prei])
break;
else
++rooti;
}
++prei;
//中序被分为[ibegin,rooti-1]rooti[rooti+1,inend]
//重建左子树和右子树
root->left=_buildTree(preorder,inorder,prei,inbegin,inorder.size()-1);
root->right=_buildTree(preorder,inorder,prei,rooti+1,inend);
}
TreeNode* buildTree(vector<int>& preorder,vector<int>& inorder)
{
int prei=0;
return _buildTree(preorder,inorder,prei,0,inorder.size()-1);
}
最左侧节点入栈,然后以图示方式访问右子树并将右子树节点值存储。
vector<int>preorderTraveler(TreeNode* root)
{
vector<int>v;
stack<TreeNode*>st;
TreeNode* cur=root;
while(cur||!st.empty())//还没到最左节点||栈中的左节点还没有遍历完
{
//1.访问左路节点,左路节点入栈
while(cur)
{
st.push(cur);
v.push_back(cur->val);
cur=cur->left;
}
//2. 栈里面的左路节点中的左子树没有访问
TreeNode* top=st.top();
st.pop();
//子问题的形式访问右子树
cur=top->right;
}
return v;
}
中序遍历的思想和前序遍历类似,只是节点的弹出顺序数据存储顺序发生变化,先找到最左节点然后数据存储,不敢先弹出。可结合图示进行理解。
vector<int>inorderTraveler(TreeNode* root)
{
vector<int>v;
stack<TreeNode*>st;
TreeNode* cur=root;
while(cur||!st.empty())
{
//1. 将左子树节点全部入栈
while(cur)
{
st.push(cur);
cur=cur->left;
}
TreeNode* top=st.top();
st.pop();
v.push_back(top->val);
//再去用子问题访问他的右子树
cur=top->right;
}
return v;
}
后序遍历:
由于不能先将节点的值进行访问,当遍历完右子树再访问根节点的值
进行push时就会可能产生是否已经被push的疑问。
那么我们就用一个prev。
如果a右子树节点被访问了,它的前面一定是他的右子树节点。
如果a的前面不是右子树节点,说明他的右子树节点还没有被访问,就需要先访问他的右子树节点。
vector<int>postorderTraveler(TreeNode* root)
{
vector<int>v;
TreeNode*prev=nullptr;
stack<TreeNode*>st;
TreeNode* cur=root;
while(cur||!st.empty())
{
//1. 将左子树节点全部入栈
while(cur)
{
st.push(cur);
cur=cur->left;
}
TreeNode*top=st.top();
//如果右子树已经访问过了,就可以直接访问top
//右子树为空或者上一个访问节点是top右孩子节点,那么表示右子树访问过了,就可以访问top了
//如果top的右子树没有访问,就可以访问top->right
if(top->right==nullptr || top->right == prev)
{
v.push_back(top->val);
prev=top;
st.pop();
}
else
cur=top->right;
}
return v;
}
有的时候,我们会感到无所事事,用很多看似无聊的方式,抱着好奇的想法去尝试一些自认为可以愉悦自己来打发时间的方式,去给自己腾出时间等待学习状态的回暖。这种思想和行为不仅不会有所帮助,很可能会将我们的状态拖得越来越差。可能一两局游戏过后,精神状态会变得更差,这时希望我们能停下来闭上眼睛休息一下,这种方式会起到最好的效果。
希望大家不忘心中理想,继续坚持走下去!!
Carry on!