本篇文章的主题是二叉树,首先介绍4个有关二叉树的操作,再分享1道二叉树的题目,最后再分享一个leetcode的隐藏用法,欢迎批评指正。
操作1:二叉树的广度优先遍历
问题分析
二叉树的广度优先遍历,即依次遍历二叉树的每层,可以通过队列结构实现。即建立一个队列,初始化队列的第一个元素是二叉树的根节点,之后每次将队列头部节点指向的根节点入队,之后将队列的头部节点出队,遍历直到队列为空。
样例输入
[3,9,21,2,4,null,7,null,null,2,6]
样例输出
3 9 21 2 4 7 2 6
最终代码
void bfs(TreeNode *root){
if(root == NULL) return ;
queue<TreeNode *> p;
p.push(root);//首先将根节点入栈
while(!p.empty()){
TreeNode *t = p.front();
printf("%d ", t->val);
if(t->left) p.push(t->left);//根据是否有左右节点判断是否入栈
if(t->right) p.push(t->right);
p.pop();//将头节点出栈
}
}
操作2:二叉树的深度优先遍历
问题分析
二叉树的深度优先遍历,即从左到右依次遍历最深的节点,可以通过递归实现。从根节点开始递归,如果存在左子树,则继续遍历左子树,左子树遍历完成后,再遍历右子树,当节点为空时返回。
样例输入
[3,9,21,2,4,null,7,null,null,2,6]
样例输出
3 9 2 4 2 6 21 7
最终代码
void dfs(TreeNode *root){
if(root == NULL) return ;
printf("%d ", root->val);
if(root->left) dfs(root->left);
if(root->right) dfs(root->right);
return ;
}
操作3:二叉树转广义表
问题分析
可以用递归的方法实现二叉树转广义表,用字符串存储转换的结果。
- 从根节点开始遍历,如果存在左子树或右子树,则输出'(';
- 如果存在左子树,则遍历左子树的结果,后输出',';
- 如果存在右子树,则遍历右子树的结果,后输出')';
- 其间需要利用sprintf函数往字符串中输出结果,并用记录sprintf函数的返回值。
样例输入
[3,9,21,2,4,null,7,null,null,2,6]
样例输出
3(9(2,4(2,6)),21(,7))
最终代码
char buff[100];
int len = 0;//用len记录输入字符的偏移量
void serialize(TreeNode *root){
if(root == NULL) return ;
len += sprintf(buff + len, "%d", root->val);
if(root->left || root ->right) {
len += sprintf(buff + len, "(");
if(root->left) serizlize(root->left);
len += sprintf(buff + len, ",");
if(root->right) serizlize(root->right);
len += sprintf(buff + len, ")");
}
return ;
}
操作4:广义表转二叉树
问题分析
我们需要根据一段存储广义表信息的字符串,还原出二叉树。此问题中二叉树的父子节点是具有包含关系的,因此可以用栈来解决问题。
- 可从左到右依次遍历字符串,如果遇到数字,则需要判断下一个字符是否是数字,并将字符串形式的数字转换为int类型。并建立新的节点,初始化节点的数值;
- 如果遇到'(',则说明后面字符串代表的二叉树,是'('前的节点的子节点,因此将'('前的节点入栈;
- '('后再遇到数值,则将新的节点设置为栈顶节点的左子树;
- 如果遇到',',则代表后续的节点为栈顶节点的右子树;
- 如果遇到')',则说明栈顶节点的子树已经连接完成,因此将栈顶节点弹出;
样例输入
3(9(2,4(2,6)),21(,7))
样例输出
[3,9,21,2,4,null,7,null,null,2,6]
最终代码
TreeNode *deserialize(char *buff){
if(buff[0] == 0) return NULL;
int i = 0;
int flag = 1;//1代表左子树,-1代表右子树
stack<TreeNode *> sta;
TreeNode *p, *root;
while(buff[i]){
printf("i = %d\n", i);
if(buff[i] >= '0' && buff[i] <= '9'){
int x = buff[i] - '0';
while(buff[i + 1] >= '0' && buff[i + 1] <= '9'){//将字符串转换为数字
i++;
x = x * 10 + buff[i] - '0';
}
p = new TreeNode(x);
if(!sta.empty()){//如果栈不为空则将p节点作为栈顶节点的子节点
if(flag == 1) sta.top()->left = p;
if(flag == -1) sta.top()->right = p;
}
else root = p;//如果栈为空,则将p作为根节点
}
if(buff[i] == '('){
sta.push(p);
flag = 1;
}
if(buff[i] == ','){
flag = -1;
}
if(buff[i] == ')'){
sta.pop();
}
i++;
}
return root;
}
题目1:二叉树的层序遍历(leetcode-102)
问题描述
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
样例输入
[3,9,21,2,4,null,7,null,null,2,6]
样例输出
[[3],[9,21],[2,4,7],[2,6]]
问题分析
此问题在二叉树层序遍历的同时,要记录遍历元素的层数。因此可以在上文的代码基础上进行修改,增设一个新变量cnt,记录每一层的节点数。依次遍历每一层的节点,当一层节点遍历完成后,栈中有且只有下一层所有的节点。
最终代码
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
TreeNode *node;
queue<TreeNode *> q;
q.push(root);
vector<vector<int>> ans;
while(!q.empty()){
if(root == NULL) return vector<vector<int>>();
int cnt = q.size();
vector<int> temp;
for(int i = 0; i < cnt; i++){
node = q.front();
temp.push_back(node->val);
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
q.pop();
}
ans.push_back(temp);
}
return ans;
}
};
总结
二叉树可以和栈、递归等知识灵活结合,尤其是在处理二叉树的左右子树时。此外,在处理二叉树时,可通过各种操作记录信息,比如题目一中的层序遍历。
此处再分享一个leetcode系统的使用技巧。例如想测试操作1至操作4的代码,但是自己又不想写生成二叉树的代码,此时可以把代码复制到leetcode的某个以二叉树为输入的题目中,例如leecode-102。复制进入后,并编写一段输出结果的代码,运行后会在“标准输出”中显示出代码的运行结果,如下图所示:
此外,leetcode是可以自己初始化测试用例的,特别方便,点击“+”即可。(而且你会发现二叉树的示意图是根据初始化的数据实时更新的!)
因此,可以把leetcode作为测试代码的工具,不需要我们去设计初始化代码,且能够非常方便的初始化数据,但缺点是有时代码运行速度较慢。