二叉树

目录

一,二叉树遍历

1,DFS、BFS

2,Morris遍历

(1)算法思路

(2)源码

(3)算法总结

3,其他遍历

力扣 199. 二叉树的右视图

力扣 366. 寻找二叉树的叶子节点

力扣 872. 叶子相似的树

二,二叉树求深度

力扣 剑指 Offer 55 - I. 二叉树的深度

力扣 111. 二叉树的最小深度

力扣 1123. 最深叶节点的最近公共祖先

力扣 1740. 找到二叉树中的距离

三,二叉树计数

1,二叉搜索树计数

2,二叉树计数

3,OJ实战

HDU 1131 Count the Trees

四,二叉树深拷贝

力扣 1490. 克隆 N 叉树

力扣 1485. 克隆含随机指针的二叉树

五,二叉树其他操作

力扣 156. 上下翻转二叉树

力扣 1666. 改变二叉树的根节点


一,二叉树遍历

1,DFS、BFS

二叉树DFS、BFS

2,Morris遍历

Morris遍历是DFS遍历的一种优化版,可以实现O(1)的空间复杂度。

(1)算法思路

按照dfs的顺序去遍历每一个节点,但是在遍历之前先记录好回溯的信息。

比如从1到2,先把5的next记为1,当遍历到5时即可凭借这个记录的回溯信息回到1,同时消除这个回溯信息,还原二叉树。

那么,我们怎么区分一个节点的右孩子是真的右孩子,还是回溯信息呢?

当遍历到1时,就要找出左孩子的右孩子的右孩子的右孩子的右孩子......,即找到了5,如果5的右孩子不存在,说明是第一次遍历到1,如果5的右孩子是1,说明已经当前不是第一次遍历到1,而是已经回溯到1了,就可以去消除回溯信息了。

(2)源码

Morris遍历方式实现的先序遍历:


#ifndef struct_TreeNode
#define struct_TreeNode
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) {}
};
#endif

void MorrisPreOrder(TreeNode* head)
{
	TreeNode* p = head;
	while (p) {
		if (p->left) {
			auto p2 = p->left;
			while (p2->right && p2->right!=p)p2 = p2->right;
			if (p2->right) {				
				p2->right = nullptr;
				p = p->right;
			}
			else {
				cout << p->val;
				p2->right = p;
				p = p->left;
			}
		}
		else {
			cout << p->val;
			p = p->right;
		}
	}
}

(3)算法总结

所谓的O(1)的空间复杂度,指的是除了二叉树本身的空间之外,所需要的空间是常数的。然而实际上算法会用到那些放了空指针的内存空间作为回溯的缓存空间,真正意义上使用过的空间,并不是O(1)的。

算法在运行的开始之前和结束之后,二叉树是不变的。但是运行过程中,二叉树有被修改,所以这个算法无法用于并行。而常规的二叉树遍历是很容易并行的。

Morris遍历和普通遍历的时间复杂度是一样的,但是由于重复遍历,Morris遍历可能会花费2倍的时间。

3,其他遍历

力扣 199. 二叉树的右视图

 题目:

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例:

输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:

   1            <---
 /   \
2     3         <---
 \     \
  5     4       <---

代码:

class Solution {
public:
	void rightSideView(TreeNode* node, vector<int>&ans, int deep) {
		if (!node)return;
		if (deep >= ans.size())ans.insert(ans.end(), 0);
		ans[deep] = node->val;
		rightSideView(node->left, ans, deep + 1);
		rightSideView(node->right, ans, deep + 1);
	}
	vector<int> rightSideView(TreeNode* root) {
		vector<int>ans;
		rightSideView(root,ans,0);
		return ans;
	}
};

力扣 366. 寻找二叉树的叶子节点

给你一棵二叉树,请按以下要求的顺序收集它的全部节点:

依次从左到右,每次收集并删除所有的叶子节点
重复如上过程直到整棵树为空
 

示例:

输入: [1,2,3,4,5]
  
          1
         / \
        2   3
       / \     
      4   5    

输出: [[4,5,3],[2],[1]]

struct Node {
	int num;
	int deep;
};
//二叉树的后序遍历
vector<Node> postorderTraversal(TreeNode* root, int &deep) {
	vector<Node>v1;
	deep = 0;
	if (root == NULL)return v1;
	int deep1,deep2;
	vector<Node>v2 = postorderTraversal(root->left, deep1);
	v1.insert(v1.end(), v2.begin(), v2.end());
	v2 = postorderTraversal(root->right, deep2);
	v1.insert(v1.end(), v2.begin(), v2.end());
	deep = max(deep1, deep2) + 1;
	v1.insert(v1.end(), { root->val,deep });	
	return v1;
}
class Solution {
public:
	vector<vector<int>> findLeaves(TreeNode* root) {
		int deep;
		vector<Node> vn = postorderTraversal(root, deep);
		vector<vector<int>>ans(deep);
		for (auto& vi : vn)ans[vi.deep - 1].push_back(vi.num);
		return ans;
	}
};

力扣 872. 叶子相似的树

请考虑一棵二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。

举个例子,如上图所示,给定一棵叶值序列为 (6, 7, 4, 9, 8) 的树。

如果有两棵二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。

如果给定的两个根结点分别为 root1 和 root2 的树是叶相似的,则返回 true;否则返回 false 。

示例 1:

输入:root1 = [3,5,1,6,2,9,8,null,null,7,4], root2 = [3,5,1,6,7,4,2,null,null,null,null,null,null,9,8]
输出:true
示例 2:

输入:root1 = [1,2,3], root2 = [1,3,2]
输出:false
 

提示:

给定的两棵树结点数在 [1, 200] 范围内
给定的两棵树上的值在 [0, 200] 范围内

struct Node {
	int num;
	int deep;
};
//二叉树的后序遍历
vector<Node> postorderTraversal(TreeNode* root, int &deep) {
	vector<Node>v1;
	deep = 0;
	if (root == NULL)return v1;
	int deep1, deep2;
	vector<Node>v2 = postorderTraversal(root->left, deep1);
	v1.insert(v1.end(), v2.begin(), v2.end());
	v2 = postorderTraversal(root->right, deep2);
	v1.insert(v1.end(), v2.begin(), v2.end());
	deep = max(deep1, deep2) + 1;
	v1.insert(v1.end(), { root->val,deep });
	return v1;
}
class Solution {
public:
	vector<vector<int>> findLeaves(TreeNode* root) {
		int deep;
		vector<Node> vn = postorderTraversal(root, deep);
		vector<vector<int>>ans(deep);
		for (auto& vi : vn)ans[vi.deep - 1].push_back(vi.num);
		return ans;
	}
	bool leafSimilar(TreeNode* root1, TreeNode* root2) {
		vector<int> v1 = findLeaves(root1)[0];
		vector<int> v2 = findLeaves(root2)[0];
		if (v1.size() != v2.size())return false;
		for (int i = 0; i < v1.size(); i++)if (v1[i] != v2[i])return false;
		return true;
	}
};

二,二叉树求深度

DFS、BFS都可以用来求深度

力扣 剑指 Offer 55 - I. 二叉树的深度

题目:

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3 。

代码:

class Solution {
public:
	int maxDepth(TreeNode* root) {
		if (!root)return 0;
		return max(maxDepth(root->left), maxDepth(root->right)) + 1;
	}
};

力扣 111. 二叉树的最小深度

题目:

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回它的最小深度  2.

代码:
 

class Solution {
public:
	int minDepth2(TreeNode* root) {
		if (!root)return 1234567890;
		if (!root->left && !root->right)return 1;
		return min(minDepth2(root->left), minDepth2(root->right)) + 1;
	}
	int minDepth(TreeNode* root) {
		if (!root)return 0;
		return minDepth2(root);
	}
};

力扣 1123. 最深叶节点的最近公共祖先

给你一个有根节点 root 的二叉树,返回它 最深的叶节点的最近公共祖先 。

回想一下:

叶节点 是二叉树中没有子节点的节点
树的根节点的 深度 为 0,如果某一节点的深度为 d,那它的子节点的深度就是 d+1
如果我们假定 A 是一组节点 S 的 最近公共祖先,S 中的每个节点都在以 A 为根节点的子树中,且 A 的深度达到此条件下可能的最大值。
 

示例 1:


输入:root = [3,5,1,6,2,0,8,null,null,7,4]
输出:[2,7,4]
解释:我们返回值为 2 的节点,在图中用黄色标记。
在图中用蓝色标记的是树的最深的节点。
注意,节点 6、0 和 8 也是叶节点,但是它们的深度是 2 ,而节点 7 和 4 的深度是 3 。
示例 2:

输入:root = [1]
输出:[1]
解释:根节点是树中最深的节点,它是它本身的最近公共祖先。
示例 3:

输入:root = [0,1,3,null,2]
输出:[2]
解释:树中最深的叶节点是 2 ,最近公共祖先是它自己。
 

提示:

树中的节点数将在 [1, 1000] 的范围内。
0 <= Node.val <= 1000
每个节点的值都是 独一无二 的。

思路:其实就是最深的那一层中,最左边的叶子节点和最右边的叶子节点的最近公共祖先。

利用后序遍历,最后一个满足左右子树都含有最深一层的叶子节点的节点就是答案。

class Solution {
public:
	TreeNode* ansNode;
	int maxDeep;
	int deep;
	//求二叉树的深度
	int maxDepth(TreeNode* root) {
		if (!root)return 0;
		return max(maxDepth(root->left), maxDepth(root->right)) + 1;
	}
	int dfs(TreeNode* root, int d) {
		if (!root)return 0;
		int a = dfs(root->left, d + 1);
		int b = dfs(root->right, d + 1);
		int ans = max(a, b) + 1;
		if (a == b && ans + d == maxDeep)ansNode = root;
		return ans;
	}
	TreeNode* lcaDeepestLeaves(TreeNode* root) {
		maxDeep = maxDepth(root);
		dfs(root, 0);
		return ansNode;
	}
};

力扣 1740. 找到二叉树中的距离

给定一棵二叉树的根节点 root 以及两个整数 p 和 q ,返回该二叉树中值为 p 的结点与值为 q 的结点间的 距离 

两个结点间的 距离 就是从一个结点到另一个结点的路径上边的数目。

示例 1:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 0
输出:3
解释:在 5 和 0 之间有 3 条边:5-3-1-0

示例 2:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 7
输出:2
解释:在 5 和 7 之间有 2 条边:5-2-7

示例 3:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 5
输出:0
解释:一个结点与它本身之间的距离为 0

提示:

  • 树中结点个数的范围在 [1, 104].
  • 0 <= Node.val <= 109
  • 树中所有结点的值都是唯一的.
  • p 和q 是树中结点的值.
class Solution {
public:
	int dp, dq, ans;
	int dfs(TreeNode* r, int p, int q,int deep)
	{
		int n = 0;
		if (r->left)n+=dfs(r->left, p, q, deep + 1);
		if (r->right)n+=dfs(r->right, p, q, deep + 1);
		if (r->val == p)dp = deep, n++;
		if (r->val == q)dq = deep, n++;
		if (n >= 2){
            ans = dp - deep + dq - deep;
            return 0;
        }
		return n;
	}
	int findDistance(TreeNode* root, int p, int q) {
		dfs(root, p, q, 0);
		return ans;
	}
};

三,二叉树计数

1,二叉搜索树计数

给定n个不同的元素,可以构建多少颗不同的二叉搜索树呢?

答案是卡特兰数(n*2)! / n! / (n+1)!

2,二叉树计数

给定n个不同的元素,可以构建多少颗不同的二叉树呢?

答案是二叉搜索树的数目再乘以n!,即(n*2)! / (n+1)!

3,OJ实战

HDU 1131 Count the Trees

题目:

Description

Another common social inability is known as ACM (Abnormally Compulsive Meditation). This psychological disorder is somewhat common among programmers. It can be described as the temporary (although frequent) loss of the faculty of speech when the whole power of the brain is applied to something extremely interesting or challenging. 
Juan is a very gifted programmer, and has a severe case of ACM (he even participated in an ACM world championship a few months ago). Lately, his loved ones are worried about him, because he has found a new exciting problem to exercise his intellectual powers, and he has been speechless for several weeks now. The problem is the determination of the number of different labeled binary trees that can be built using exactly n different elements. 

For example, given one element A, just one binary tree can be formed (using A as the root of the tree). With two elements, A and B, four different binary trees can be created, as shown in the figure. 
 
If you are able to provide a solution for this problem, Juan will be able to talk again, and his friends and family will be forever grateful. 

Input

The input will consist of several input cases, one per line. Each input case will be specified by the number n ( 1 ≤ n ≤ 100 ) of different elements that must be used to form the trees. A number 0 will mark the end of input and is not to be processed. 

Output

For each input case print the number of binary trees that can be built using the n elements, followed by a newline character. 

Sample Input

1
2
10
25
0

Sample Output

1
4
60949324800
75414671852339208296275849248768000000

题意:

二叉树计数,答案是(n*2)! / (n+1)!

import java.util.*;
import java.math.BigInteger;
public class Main {
    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        while(true)
        {
        	int n=Integer.parseInt(cin.nextLine());
        	if(n==0)break;
        	BigInteger s=new BigInteger("1");
        	for(int i=n+2;i<=n*2;i++)s=s.multiply(BigInteger.valueOf(i));
                System.out.println(s.toString());
        }        
    }
}

四,二叉树深拷贝

力扣 1490. 克隆 N 叉树

给定一棵 N 叉树的根节点 root ,返回该树的深拷贝(克隆)。

N 叉树的每个节点都包含一个值( int )和子节点的列表( List[Node] )。

class Node {
    public int val;
    public List<Node> children;
}

N 叉树的输入序列用层序遍历表示,每组子节点用 null 分隔(见示例)。

示例 1:

输入:root = [1,null,3,2,4,null,5,6]
输出:[1,null,3,2,4,null,5,6]

示例 2:

输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]

提示:

  • 给定的 N 叉树的深度小于或等于 1000
  • 节点的总个数在 [0, 10^4] 之间
class Solution {
public:
    Node* cloneTree(Node* root) {
        if(!root)return root;
        Node* ans=cloneNode(root);
        for(auto chd:root->children)ans->children.push_back(cloneTree(chd));
        return ans;
    }
    Node* cloneNode(Node* root) {
        if(!root)return root;
        Node* ans=new Node();
        ans->val=root->val;
        return ans;
    }
};

力扣 1485. 克隆含随机指针的二叉树

给你一个二叉树,树中每个节点都含有一个附加的随机指针,该指针可以指向树中的任何节点或者指向空(null)。

请返回该树的 深拷贝 。

该树的输入/输出形式与普通二叉树相同,每个节点都用 [val, random_index] 表示:

  • val:表示 Node.val 的整数
  • random_index:随机指针指向的节点(在输入的树数组中)的下标;如果未指向任何节点,则为 null 。

该树以 Node 类的形式给出,而你需要以 NodeCopy 类的形式返回克隆得到的树。NodeCopy 类和Node 类定义一致。

示例 1:

输入:root = [[1,null],null,[4,3],[7,0]]
输出:[[1,null],null,[4,3],[7,0]]
解释:初始二叉树为 [1,null,4,7] 。
节点 1 的随机指针指向 null,所以表示为 [1, null] 。
节点 4 的随机指针指向 7,所以表示为 [4, 3] 其中 3 是树数组中节点 7 对应的下标。
节点 7 的随机指针指向 1,所以表示为 [7, 0] 其中 0 是树数组中节点 1 对应的下标。

示例 2:

输入:root = [[1,4],null,[1,0],null,[1,5],[1,5]]
输出:[[1,4],null,[1,0],null,[1,5],[1,5]]
解释:节点的随机指针可以指向它自身。

示例 3:

输入:root = [[1,6],[2,5],[3,4],[4,3],[5,2],[6,1],[7,0]]
输出:[[1,6],[2,5],[3,4],[4,3],[5,2],[6,1],[7,0]]

提示:

  • tree 中节点数目范围是 [0, 1000]
  • 每个节点的值的范围是 [1, 10^6]
class Solution {
public:
    NodeCopy* copyRandomBinaryTree(Node* root) {
        NodeCopy* ans=cloneTree(root);
        for(auto par:random)trans[par.first]->random=trans[par.second];
        return ans;
    }
    NodeCopy* cloneTree(Node* root) {
        if(!root)return nullptr;
        NodeCopy* ans=cloneNode(root);
        ans->left=cloneTree(root->left);
        ans->right=cloneTree(root->right);
        return ans;
    }
    NodeCopy* cloneNode(Node* root) {
        if(!root)return nullptr;
        NodeCopy* ans=new NodeCopy();
        ans->val=root->val;
        if(root->random)random[root]=root->random;
        trans[root]=ans;
        return ans;
    }
    map<Node*,NodeCopy*>trans;
    map<Node*,Node*>random;
};

五,二叉树其他操作

力扣 156. 上下翻转二叉树

给定一个二叉树,其中所有的右节点要么是具有兄弟节点(拥有相同父节点的左节点)的叶节点,要么为空,将此二叉树上下翻转并将它变成一棵树, 原来的右节点将转换成左叶节点。返回新的根。

例子:

输入: [1,2,3,4,5]

    1
   / \
  2   3
 / \
4   5

输出: 返回二叉树的根 [4,5,2,#,#,3,1]

   4
  / \
 5   2
    / \
   3   1  
说明:

对 [4,5,2,#,#,3,1] 感到困惑? 下面详细介绍请查看 二叉树是如何被序列化的。

二叉树的序列化遵循层次遍历规则,当没有节点存在时,'#' 表示路径终止符。

这里有一个例子:

   1
  / \
 2   3
    /
   4
    \
     5
上面的二叉树则被序列化为 [1,2,3,#,#,4,#,#,5].

思路:

先把左指针构成的链表进行反转,再把所有的右孩子往上提,最后所有节点的左右孩子互换。

//链表反转
TreeNode * Reverse(TreeNode *p)
{
    if(p==NULL)return p;
    TreeNode * q1;
    TreeNode * q2;
    q1=p->left, p->left=NULL;
    while(q1)
    {
        q2=q1->left, q1->left=p, p=q1, q1=q2;
    }
    return p;
}
 
class Solution {
public:
    TreeNode* upsideDownBinaryTree(TreeNode* root) {
        if(!root)return NULL;
        root = Reverse(root);
        TreeNode*p=root,*q=root;
        while(p->left)p->right=p->left->right,p=p->left;
        p->right=NULL;
        while(q)p=q->left,q->left=q->right,q->right=p,q=q->right;
        return root;
    }
};

力扣 1666. 改变二叉树的根节点

给定一棵二叉树的根节点 root 和一个叶节点 leaf ,更改二叉树,使得 leaf 为新的根节点。

你可以按照下列步骤修改从 leaf 到 root 的路径中除 root 外的每个节点 cur :

如果 cur 有左子节点,则该子节点变为 cur 的右子节点。注意我们保证 cur 至多有一个子节点。
cur 的原父节点变为 cur 的左子节点。
返回修改后新树的根节点。

注意:确保你的答案在操作后正确地设定了 Node.parent (父节点)指针,否则会被判为错误答案。

示例 1:


输入: root = [3,5,1,6,2,0,8,null,null,7,4], leaf = 7
输出: [7,2,null,5,4,3,6,null,null,null,1,null,null,0,8]
示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], leaf = 0
输出: [0,1,null,3,8,5,null,null,null,6,2,null,null,7,4]
 

提示:

树中节点的个数在范围 [2, 100] 内。
-109 <= Node.val <= 109
所有的 Node.val 都是唯一的。
leaf 存在于树中。

/*
class Node {
public:
	int val;
	Node* left;
	Node* right;
	Node* parent;
};
*/
class Solution {
public:
	Node* p = NULL;
	void flip(Node * leaf) {
		Node* pa = leaf->parent;
		if (!pa) {
			leaf->parent = p;
			return;
		}
		if (leaf->left)leaf->right = leaf->left, leaf->left = NULL;
		if (pa->left == leaf)pa->left = NULL;
		if (pa->right == leaf)pa->right = NULL;
		leaf->left = pa, leaf->parent = p, p = leaf;
		if(pa)flip(pa);
	}
	Node* flipBinaryTree(Node* root, Node * leaf) {
		p = NULL;
		flip(leaf);
		return leaf;
	}
};

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值