二叉树的翻转也称二叉树的镜像,是算法面试中比较简单的一种二叉树问题:
1.如何理解二叉树的镜像:
其实就是某个节点为根节点将它的左右子树交换位置,如下图:(线条不直请不要在意,作者手残划不直,(/≧▽≦)/)
输入:[8,7,4,3,5,1,2]
输出:[8,4,7,2,1,5,3]
2.实现思路:
2.1.使用递归思想实现:
将整棵树看作一个根节点和他的左右两颗子树即左子树和右子树
然后再将左子树的根节点看作新的根节点其左右子树同理
以此不断递归,右子树同理,所以思路就很清晰了:
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*代码中的方法名、参数名已经指定,请勿修改,直接返回方法规定的值(根节点)即可
*/
TreeNode* Mirror(TreeNode* pRoot) {
// 检查当前节点是否为空
if(pRoot == nullptr)
// 如果当前节点为空,直接返回空指针
return nullptr;
else{
// 递归调用Mirror函数,交换当前节点的左子树
TreeNode *pr = Mirror(pRoot->left);
// 将当前节点的左子指针指向其右子树的镜像
pRoot->left = Mirror(pRoot->right);
// 将当前节点的右子指针指向保存的左子树的镜像
pRoot->right = pr;
}
// 返回当前节点,此时其左右子树已经交换
return pRoot;
}
此算法递归调用次数和树的高度有关所以时间复杂度为O(n)级。
1.首先检查整颗树的根节点是否为空,为空则不需要翻转左右子树,返回空指针(因为Mirror函数的返回值是TreeNode类型的指针):
if(pRoot == nullptr)
return nullptr;
2.然后节点不为空则翻转左右子树:
else{
TreeNode *pr = Mirror(pRoot->left);
pRoot->left = Mirror(pRoot->right);
pRoot->right = pr;
}
这里定义了一个TreeNode类型的指针pr指向根节点的左子树的根节点,pr用来临时储存左子树根节点的地址,于此同时递归调用Mirror函数将其左子树的左右子树进行交换,将交换后的数的根节点返回。(Mirror(pRoot->left)函数返回的是左子树及其每个节点所有子树的交换完毕后的根节点的指针,并将其赋给pr)。
然后再将根节点的右子树的根节点赋给根节点的左子树的根结点也就是pRoot->left,同时递归调用Mirror()将右子树的所有子树都进行交换,最后将pr存储的左子树的根节点赋给右子树的根节点也就是pRoot->right,到这里整颗树交换就已经完成了。
最后return 整颗树的根节点的指针。
TreeNode *pr = Mirror(pRoot->left);
上面这行代码的作用是将整颗子树的根节点的左子树和所有子树进行交换后的左子树存到pr。
pRoot->left = Mirror(pRoot->right);
上面 这行代码的作用是将整颗子树的根节点的右子树和所有子树交换后的右子树赋给根节点的左子树位置。
pRoot->right = pr;
上面则是将先前pr存的内容赋给根节点的右子树。
下面是对此代码的详解:
-
TreeNode* Mirror(TreeNode* pRoot) {
: 定义了一个名为Mirror
的函数,它接受一个指向二叉树节点的指针pRoot
作为参数,并返回一个指向二叉树节点的指针。 -
if(pRoot == nullptr)
: 检查传入的节点是否为nullptr
,即检查这个节点是否是空节点。 -
return nullptr;
: 如果节点是空的,那么没有子树可以翻转,直接返回nullptr
。 -
TreeNode *pr = Mirror(pRoot->left);
: 递归地调用Mirror
函数来翻转当前节点的左子树,并将返回的新的根节点(左子树的镜像)保存在变量pr
中。 -
pRoot->left = Mirror(pRoot->right);
: 递归地调用Mirror
函数来翻转当前节点的右子树,并将返回的新的根节点(右子树的镜像)赋值给当前节点的左子指针。 -
pRoot->right = pr;
: 将之前保存的左子树的镜像(变量pr
)赋值给当前节点的右子指针,从而完成左右子树的交换。 -
return pRoot;
: 在完成当前节点的左右子树交换后,返回当前节点作为新的子树的根节点。
2.2.使用迭代思想解决 :
迭代思想则是通过对整棵树进行深度优先遍历在遍历的过程中对其左右子树进行交换,最后得到交换过后的二叉树的根节点指针:
1.建立一个栈sta,并将根节点pRoot压入栈顶。
2.此时栈中含有一个栈顶元素 sta.top()即pRoot,当栈不为空时,继续遍历二叉树,遍历过的元素先入栈,将其左右子树交换后出栈。
3.遍历结束时栈为空,整棵树也交换完毕,最后返回整棵树的根节点指针。(栈空则说明所有元素已经进栈并出栈)
代码中使用的栈(satck)库中的函数(记得在头文件加入#include<stack>):
1.sta.empty() 检验是否栈空,空的返回true,非空则返回false。
2.sta.top() 输入栈顶元素,但是不出栈。
3.sta.pop() 将栈顶元素出栈。
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*代码中的方法名,参数名已经指定,请勿修改,直接返回方法规定的值(根节点)即可
*/
class Solution{
public:
stack<TreeNode*> sta; // 定义一个栈,用于遍历树的节点
TreeNode* rot; // 用于记录栈顶元素
TreeNode* tmp; // 用于临时保存来交换左右子节点
TreeNode* Mirror(TreeNode* pRoot) {
if(pRoot == nullptr){
return nullptr; // 如果根节点为空,直接返回空
}
sta.push(pRoot); // 将根节点压入栈中
while(!sta.empty()){ // 当栈不为空时,继续遍历
rot = sta.top(); // 取出栈顶元素
sta.pop(); // 将栈顶元素弹出栈
// 如果当前节点的左子节点不为空,将其压入栈中
if(rot->left != nullptr){
sta.push(rot->left);
}
// 如果当前节点的右子节点不为空,将其压入栈中
if(rot->right != nullptr){
sta.push(rot->right);
}
// 交换当前节点的左右子节点
tmp = rot->left;
rot->left = rot->right;
rot->right = tmp;
}
return pRoot; // 返回翻转后的树的根节点
}
};
大概过程如下,鼠标写字不太习惯qwq:
同样此算法的时间复杂度为O(n)级。
代码详解:
- 检查传入的根节点是否为空,如果为空,则直接返回空。
- 使用一个栈
sta
来进行深度优先搜索(DFS)遍历树。 - 在遍历过程中,每次从栈中取出一个节点,然后交换其左右子节点。
- 如果当前节点的左子节点不为空,将其压入栈中。
- 如果当前节点的右子节点不为空,将其压入栈中。
- 继续这个过程,直到栈为空,此时所有的节点都已经被访问并交换了左右子节点。
- 最后返回根节点,此时整个树已经被翻转。