二叉树 Tree
二叉树(Tree)
1、定义
树是一种比较重要的数据结构,尤其是二叉树。二叉树是一种特殊的树,在二叉树中每个节点最多有两个子节点,一般称为左子节点和右子节点(或左孩子和右孩子),并且二叉树的子树有左右之分,其次序不能任意颠倒。二叉树是递归定义的,因此,与二叉树有关的题目基本都可以用递归思想解决,当然有些题目非递归解法也应该掌握,如非递归遍历节点等等。
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
2、二叉树之前序、中序、后序遍历
2.1、前序遍历
1、题目分析:用到stack来辅助运算。由于先序遍历的顺序是"根-左-右", 算法为:1. 把根节点push到栈中;2. 循环检测栈是否为空,若不空,则取出栈顶元素,保存其值,然后看其右子节点是否存在,若存在则push到栈中。再看其左子节点,若存在,则push到栈中。
class Solution {
public:
vector<int>preorderTraversal(TreeNode *root)
{
vector<int> path;
if(root == NULL)
return path;
stack<TreeNode *> s;
TreeNode *p = root;
while(p != NULL || !s.empty())
{
while(p != NULL)
{
path.push_back(p->val);
s.push(p);
p = p->left;
}
if(!s.empty())
{
p = s.top();
s.pop();
p = p->right;
}
}
return path;
}
};
vector<int>preorderTraversal(TreeNode *root)
{
vector<int> path;
if(root == NULL)
return path;
stack<TreeNode *> s;
s.push(root);
TreeNode *p = NULL;
while( !s.empty())
{
p = s.top();
s.pop():
path.push_back(p->val);
if(p->right != NULL)
{
s.push(p->right);
}
if(p->left != NULL)
{
s.push(p->left);
}
}
return path;
}
2.2、中序遍历
void inorderTraversal(TreeNode *root, vector<int> &path)
{
stack<TreeNode *> s;
TreeNode *p = root;
while(p != NULL || !s.empty())
{
while(p != NULL)
{
s.push(p);
p = p->left;
}
if(!s.empty())
{
p = s.top();
path.push_back(p->val);
s.pop();
p = p->right;
}
}
}
2.3、后序遍历
vector<int> posOrderUnRecur1(TreeNode* root) {
vector<int> path;
if(root == NULL)
return path;
if (root != NULL) {
stack<TreeNode*> s1 ;
stack<TreeNode*> s2 ;
s1.push(root);
while (!s1.empty()) {
root = s1.top();
s1.pop();
s2.push(root);
if (root->left != NULL) {
s1.push(root->left);
}
if (root->right != NULL) {
s1.push(root->right);
}
}
while (!s2.empty()) {
TreeNode* tmp = s2.top();
s2.pop();
path.push_back(tmp->val);
}
}
3、二叉树之层遍历
层次遍历
void breadthFirstSearch(TreeNode* root){
queue<TreeNode*> nodeQueue;
nodeQueue.push(root);
while (!nodeQueue.empty()) {
root = nodeQueue.front();
cout << root->data;
nodeQueue.pop();
if (root->lchild) {
nodeQueue.push(root->lchild); // 先把左子树入队
}
if (root->rchild) {
nodeQueue.push(root->rchild); // 再把右子树入队
}
}
}
把二叉树打印成多行
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
这里不就是层序遍历?可以采用上一题的解题策略,不过简单起见,将栈换成队列,并且没有方向之分,都是从左到右。
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> >result;
if(pRoot == NULL)
return result;
queue<TreeNode*> q;
q.push(pRoot);
TreeNode* pHead;
while(! q.empty())
{
vector<int>temp;
int count = 0;
int length = q.size();
while((count++) < length)
{
pHead = q.front();
temp.push_back(pHead->val);
q.pop();
if(pHead->left != NULL)
q.push(pHead->left);
if(pHead->right != NULL)
q.push(pHead->right);
}
result.push_back(temp);
}
return result;
}
};
之字形打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
本题使用两个栈作为辅助容器。打印某一层全部节点时,把下一层的子节点保存到另一个栈内。如果当前打印的是奇数层,则先保存左子树节点再保存右子树节点到奇数栈内;如果当前打印的是偶数层,则先保存右子树在保存左子树节点到偶数栈内。
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > result;
if(pRoot == NULL)
return result;
stack<TreeNode*>odd_s;
stack<TreeNode*>even_s;
odd_s.push(pRoot);
TreeNode* pHead;
while(!odd_s.empty() || !even_s.empty())
{
vector<int>temp;
if(!odd_s.empty())
{
while(!odd_s.empty())
{
pHead = odd_s.top();
temp.push_back(pHead->val);
odd_s.pop();
if(pHead->left != NULL)
even_s.push(pHead->left);
if(pHead->right != NULL)
even_s.push(pHead->right);
}
result.push_back(temp);
}
else
{
while(!even_s.empty())
{
pHead = even_s.top();
temp.push_back(pHead->val);
even_s.pop();
if(pHead->right != NULL)
odd_s.push(pHead->right);
if(pHead->left != NULL)
odd_s.push(pHead->left);
}
result.push_back(temp);
}
}
return result;
}
};
4、二叉树之深度相关
4.1、二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
题目要求:给定一个二叉树,找出它的最大深度。 最大深度是指的从根节点一直到最远的叶节点中所有的节点数目。
题目分析:求二叉树的最大深度问题用到深度优先搜索DFS,递归的完美应用。
题目解答:
==递归==
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == nullptr) return 0;
if(root->left == nullptr && root->right == nullptr) return 1;
int maxleft = maxDepth(root->left);
int maxright = maxDepth(root->right);
return max(maxleft, maxright) + 1;
}
};
==非递归==
```cpp
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL)
return 0;
int maxDeep = 0;
queue <TreeNode*>q;
//根节点入队
q.push(root);
while (!q.empty()) {
int width = q.size();
//每遍历一层深度+1
maxDeep += 1;
//注意这里循环的次数是width,出队的仅仅是每一层的元素
for (int i = 0; i < width; i++) {
TreeNode* nodeTemp = q.front();
q.pop();
if (nodeTemp->left != NULL) {
q.push(nodeTemp->left);
}
if(nodeTemp->right != NULL) {
q.push(nodeTemp->right);
}
}
}
return maxDeep;
}
};
4.2、平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false 。
题目要求:求二叉树是否平衡,根据题目中的定义,高度平衡二叉树是每一个结点的两个子树的深度差不能超过1。
题目分析:我们肯定需要一个求各个点深度的函数,然后对每个节点的两个子树来比较深度差,时间复杂度为O(NlgN)。
题目解答:
class Solution {
public:
bool isBalanced(TreeNode* root) {
if(root == NULL)
return true;
int left = TreeDepth(root->left);
int right = TreeDepth(root->right);
return abs(left -right) <= 1 &&
isBalanced(root->left) &&
isBalanced(root->right);
}
int TreeDepth(TreeNode* root)
{
if(root == NULL)
return 0;
return 1 + max(TreeDepth(root->left),TreeDepth(root->right));
}
};
4.3、二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
给定二叉树
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。
题目要求:求二叉树的直径,并告诉了我们直径就是两点之间的最远距离。
题目分析:我们只要对每一个结点求出其左右子树深度之和,这个值作为一个候选值,然后再对左右子结点分别调用求直径对递归函数,这三个值相互比较,取最大的值更新结果res,因为直径不一定会经过根结点,所以才要对左右子结点再分别算一次。
题目解答:
class Solution {
public:
int res = 0;
int diameterOfBinaryTree(TreeNode* root) {
diameterTree(root);
return res;
}
int diameterTree(TreeNode* root) {
if(root == NULL)
return 0;
int left = diameterTree(root->left);
int right = diameterTree(root->right);
res = max(res,left + right);
return max(left,right) +1;
}
};
4.4、二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最小深度 2.
非递归做法
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == NULL)
return 0;
int minDeep = 0;
queue <TreeNode*>q;
//根节点入队
q.push(root);
while (!q.empty()) {
int width = q.size();
//每遍历一层深度+1
minDeep += 1;
//注意这里循环的次数是width,出队的仅仅是每一层的元素
for (int i = 0; i < width; i++) {
TreeNode* nodeTemp = q.front();
q.pop();
if(nodeTemp->left == NULL && nodeTemp->right == NULL)
return minDeep;
if (nodeTemp->left != NULL) {
q.push(nodeTemp->left);
}
if(nodeTemp->right != NULL) {
q.push(nodeTemp->right);
}
}
}
return minDeep;
}
};
递归做法
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == nullptr) return 0;
if(root->left == nullptr && root->right == nullptr) return 1;
int minleft = minDepth(root->left);
int minright = minDepth(root->right);
if(minleft == 0 || minright == 0) return minleft + minright + 1;
else return 1 + min(minDepth(root->left), minDepth(root->right));
}
};
4.5、二叉树的坡度
给定一个二叉树,计算整个树的坡度。
一个树的节点的坡度定义即为,该节点左子树的结点之和和右子树结点之和的差的绝对值。空结点的的坡度是0。
整个树的坡度就是其所有节点的坡度之和。
示例:
输入:
1
/ \
2 3
输出:1
解释:
结点 2 的坡度: 0
结点 3 的坡度: 0
结点 1 的坡度: |2-3| = 1
树的坡度 : 0 + 0 + 1 = 1
提示:
任何子树的结点的和不会超过 32 位整数的范围。
坡度的值不会超过 32 位整数的范围。
class Solution {
public:
int res = 0;
int findTilt(TreeNode* root) {
TreeSum(root) ;
return res;
}
int TreeSum(TreeNode* root)//,int& res
{
if(root == NULL)
return 0;
int left = TreeSum(root->left);
int right = TreeSum(root->right);
res += abs(left - right);
return (left + right + root->val);
}
};
5、二叉树之侧视图相关
5.1、二叉树每一行最右边的一个数字
题目要求:这道题要求我们打印出二叉树每一行最右边的一个数字。
题目分析:实际上是求二叉树层序遍历的一种变形,我们只需要保存每一层最右边的数字即可,这道题只要在之前层遍历那道题上稍加修改即可得到结果,还是需要用到数据结构队列queue,遍历每层的节点时,把下一层的节点都存入到queue中,每当开始新一层节点的遍历之前,先把新一层最后一个节点值存到结果中。
题目解答:
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
if(root == nullptr) return res;
queue<TreeNode*> que;
que.push(root);
while(que.size()){
res.push_back(que.back()->val);
int len = que.size();
for(int i = 0; i < len; i++){
TreeNode* node = que.front();
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return res;
}
};
6、二叉树之递归应用
6.1、判断两棵树是否相同
题目要求:判断两棵树是否相同。
题目分析:利用深度优先搜索DFS来递归。
题目解答:
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if(p == nullptr && q == nullptr) return true;
if(p == nullptr || q == nullptr) return false;
if(p->val != q->val) return false;
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};
6.2、判断一棵树是否对称
题目要求:判断一棵树是否对称。
题目分析:利用深度优先搜索DFS来递归。
题目解答:
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return root == nullptr || isSymmetricTree(root->left, root->right);
}
bool isSymmetricTree(TreeNode* p, TreeNode* q) {
if(p == nullptr && q == nullptr) return true;
if(p == nullptr || q == nullptr) return false;
if(p->val != q->val) return false;
return isSymmetricTree(p->left, q->right) && isSymmetricTree(p->right, q->left);
}
};
6.3、翻转二叉树
题目要求:这道题让我们翻转二叉树。
题目分析:利用三种方法求解问题。
题目解答:
class Solution {
public:
TreeNode* invertTree1(TreeNode* root) {
if(root == NULL)
return root;
TreeNode* temp = root->left;
root->left = root->right;
root->right = temp;
root->left = invertTree(root->left);
root->right = invertTree(root->right);
return root;
}
};
TreeNode* invertTree2(TreeNode* root){
stack<TreeNode*> stk;
stk.push(root);
TreeNode *cur = root;
while(stk.size()){
cur = stk.top();
stk.pop();
if(cur){
stk.push(cur->left);
stk.push(cur->right);
swap(cur->left, cur->right);
}
}
return root;
}
TreeNode* invertTree3(TreeNode* root){
queue<TreeNode*> que;
que.push(root);
TreeNode *cur;
while(que.size()){
cur = que.front();
que.pop();
if(cur){
que.push(cur->left);
que.push(cur->right);
swap(cur->left, cur->right);
}
}
return root;
}
};
6.4、一个数是否是另一个树的子树
题目要求:这道题让我们求一个数是否是另一个树的子树。
题目分析:子树必须是从叶结点开始的,中间某个部分的不能算是子树,那么我们转换一下思路,是不是从s的某个结点开始,跟t的所有结构都一样,那么问题就转换成了判断两棵树是否相同,也就是两棵树是否相同的问题了,用递归来写十分的简洁,我们先从s的根结点开始,跟t比较,如果两棵树完全相同,那么返回true,否则就分别对s的左子结点和右子结点调用递归再次来判断是否相同,只要有一个返回true了,就表示可以找得到。
题目解答:
class Solution {
public:
bool isSubtree(TreeNode* s, TreeNode* t) {
if(s == nullptr) return false;
if(isSameTree(s, t)) return true;
return isSubtree(s->left, t) || isSubtree(s->right, t);
}
bool isSameTree(TreeNode* p, TreeNode* q) {
if(p == nullptr && q == nullptr) return true;
if(p == nullptr || q == nullptr) return false;
if(p->val != q->val) return false;
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};
参考
1、https://blog.csdn.net/pushup8/article/details/86014556