学习数据结构和算法,二叉树是不可能避过去的一环,而且是很重要的一环,之后的很多算法思想、编程模式其实在二叉树的相关内容中有所体现,学好二叉树,深入理解二叉树,深入理解递归思想,将会帮助我们在之后的学习中形成一个闭环,打通任督二脉。话不多说,开始学习。
1、树的结构:在数据结构中,不管再复杂的结构,无非是由数组或链表组成的,树和图只是有许多分支的复杂链表。二叉树,不用说,每个结点最多有两个后续的链表。知道了是链表的结构,就不难理解树的结构。
2、树的相关概念:
根结点:顾名思义,树的第一个结点,当然有时我们会声明一个头结点用来指向根
叶子结点:树最下层的结点,没有孩子
孩子结点和双亲结点:类似链表中后继和前驱的关系
树的深度:表示有几层
度:在图中也有这个概念,一个结点的有几个孩子,就有几个度,树的度等于最大的结点度
3、树的常见表达法:
双亲表示法:每个结点包含了指向父母的指针
孩子表示法:每个结点包含了指向孩子的指针,所有孩子组成一个线性表
双亲孩子表示法:上述结合
孩子兄弟表示法:二叉树的表示方法
前序
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
//root->左子树->右子树
void help(TreeNode* root,vector<int>& res){
if(root==NULL)
return;
res.push_back(root->val);
help(root->left,res);
help(root->right,res);
return;
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
help(root,res);
return res;
}
};
中序
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
//左子树->root->右子树
void help(TreeNode* root,vector<int>& res){
if(root==NULL)
return;
help(root->left,res);
res.push_back(root->val);
help(root->right,res);
return;
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
help(root,res);
return res;
}
};
后序
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
//左子树->右子树->root
void help(TreeNode* root,vector<int>& res){
if(root==NULL)
return;
help(root->left,res);
help(root->right,res);
res.push_back(root->val);
return;
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
help(root,res);
return res;
}
};
递归方法没有什么好讲的了,完全是按照三种遍历的定义来的。
将递归改为迭代,往往需要利用栈或者队列,就像是将dfs改为bfs。
前序
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if(!root)
return res;
stack<TreeNode*> s;
s.push(root);
TreeNode* p=root;
while(!s.empty()){
p=s.top();s.pop();
res.push_back(p->val);
if(p->right)
s.push(p->right);
if(p->left)
s.push(p->left);
}
return res;
}
};
中序
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
if(!root)
return res;
stack<TreeNode*> s;
TreeNode* p=root;
//s.push(root);
while(p || !s.empty()){
while(p){//找到当前子树的最深左叶子结点
s.push(p);
p=p->left;
}
p=s.top();s.pop();
res.push_back(p->val);
p=p->right;
}
return res;
}
};
层序遍历,顾名思义,是按照顺序对二叉树一层一层的遍历,显然就是广度优先搜索BFS。
敲黑板,背模板,BFS搭配队列结构才香。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if(!root)
return res;
TreeNode* p=root;
queue<TreeNode*> q;
q.push(root);
//bfs核心
int count=1;
while(!q.empty()){
vector<int> temp;
int deepSize=q.size();
while(deepSize){
p=q.front();
temp.push_back(p->val);
if(p->left){
q.push(p->left);
}
if(p->right){
q.push(p->right);
}
deepSize--;
q.pop();
}
res.push_back(temp);
}
return res;
}
};
这个有点和层序遍历类似,不过用递归dfs的思想也很好理解。对每一层的左右子树都进行递归,取最大的深度为结果就可以。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if(!root)
return 0;
int res=0;
//bfs
/*TreeNode* p=root;
queue q;
q.push(root);
while(!q.empty()){
int deepSize=q.size();//每层的叶子数量
while(deepSize){
p=q.front();
if(p->left){
q.push(p->left);
}
if(p->right){
q.push(p->right);
}
deepSize--;
q.pop();
}
res++;
}*/
//dfs
res=dfs(root,res);
return res;
}
int dfs(TreeNode* root,int deep){
if(!root)
return deep;
deep++;
int leftD=deep,rightD=deep;
leftD=dfs(root->left,deep);
rightD=dfs(root->right,deep);
deep=max(leftD,rightD);
return deep;
}
};
最大宽度的解法,是一种很明显的利用二叉树层序性质的题目,二叉树的层序编号结论:对于编号为n的结点,其左孩子编号为2n,右孩子编号为2n+1。二叉树某一层的宽度,等于本层最后一个结点与第一个结点的编号之差加1,利用递归的思想,深搜来解决,利用一个哈希表来记录当前深度的第一个编号。如下。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
// dfs
//遍历每一个结点,对于每一层,第一个遍历到的结点为计算当前层宽度的起始位置
//记录每一个结点的编号,pos,所在的宽度为pos-left+1
int width=0;
unordered_map<int,unsigned long long> book;
void dfs(TreeNode* root,unsigned long long pos,int deep){
if(!root)
return;
if(book.find(deep)==book.end())
book[deep]=pos;
width=width>(pos-book[deep]+1) ? width : (pos-book[deep]+1);
dfs(root->left,2*pos,deep+1);
dfs(root->right,2*pos+1,deep+1);
}
int widthOfBinaryTree(TreeNode* root) {
if(!root)
return 0;
book[1]=1;
dfs(root,1,1);
return width;
}
};
非常经典的问题,但是不知道套路的童鞋可能一时无法着手。我们先来看一看代码。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
private:
unordered_map<int,int> index;
public:
TreeNode* help(vector<int>& preorder,vector<int>& inorder,
int left_p,int right_p,int left_i,int right_i){
if(left_p>right_p || left_i>right_i)
return NULL;
int pre_root=left_p;//前序中的root
int in_root=index[preorder[left_p]];//中序中的root
int l_leftTree=in_root-left_i;//中序遍历中左子树的长度
TreeNode *root=new TreeNode(preorder[pre_root]);
root->left=help(preorder,inorder,left_p+1,left_p+l_leftTree,left_i,in_root-1);
root->right=help(preorder,inorder,left_p+l_leftTree+1,right_p,in_root+1,right_i);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int size_p=preorder.size();
int size_i=inorder.size();
//建立哈希表
for(int i=0;i<size_i;i++){
index[inorder[i]]=i;
}
return help(preorder,inorder,0,size_p-1,0,size_i-1);
}
};
不妨看一看递归中的最后两步,给新建的root插入左右子结点,是不是有点熟悉。返回去看一看,不就是树的前序遍历吗?凡是涉及到二叉树的题目,递归思路是真的香,因为这一类几乎都是以树的遍历方法为原型的,只是其他的步骤可能比遍历的读取较为复杂,但实际上,都是一个编程模板。接下来再看一看根据中序和后续遍历的结果构造树。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
//中序遍历结构:左子树,根,右子树
//后序遍历结构:左子树,右子树,根
unordered_map<int,int> book;
TreeNode* help(const vector<int>& inorder,const vector<int>& postorder,
int in_left,int in_right,int post_left,int post_right){
if(post_left>post_right)
return nullptr;
//从后序遍历中获取根节点,即最后一个
int post_root=post_right;
//在中序遍历中定位
int in_root=book[postorder[post_right]];
//左子树长度
int len_left=in_root-in_left;
//建立头结点
TreeNode* root=new TreeNode(postorder[post_root]);
//建立左右子节点
root->left=help(inorder,postorder,in_left,in_root-1,post_left,post_left+len_left-1);
root->right=help(inorder,postorder,in_root+1,in_right,post_left+len_left,post_right-1);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int n=inorder.size();
//记录中序,方便递归时查找
for(int i=0;i<n;i++){
book[inorder[i]]=i;
}
TreeNode* root=help(inorder,postorder,0,n-1,0,n-1);
return root;
}
};
显然,递归构造的过程也是一个前序遍历的模板扩展。
1、构造一棵树的问题可以分解成构造若干子树的问题,即相同子问题都在构造一棵树,因此,每一次递归向下去构造一棵子树。
2、确定遍历结果中,根结点的位置,前序遍历的结构为:根结点,左子树,右子树
,中序遍历的结构为:左子树,根结点,右结点
,后续遍历的结构为左子树,右子树,根结点
3、在递归函数中,先建立root,在递归去建立root的左右子树,注意左右子树在遍历结果的范围,直到越界为递归终止条件。
^ _ ^编辑不易,水平有限,大家一起学习。