1.括号表示法构造一棵树
下面的代码都使用下面的图测试:
这颗树的前序遍历是
- [A,B,D,G,C,E,F,H]
为了程序的完整性,我想先构建一颗上述树,知识匮乏的我只能使用括号表示法构造:
- “A(B(,D(G,)),C(E,F(H,)))”
1.用一个类表示树的节点
class Node
{
public:
int val;
vector<Node*> children; //每个Node节点包含一个children数组,数组元素是Node*类型,表示指向其它的节点
//三个构造函数
Node() {}
Node(int _val)
{
val = _val;
}
Node(int _val, vector<Node*> _children)
{
val = _val;
children = _children;
}
};
2.使用括号表示法构建树
void CreateTree(Node *&root,string &s)
{
stack<Node*>tree;
Node* p = NULL;
int i =0;
char ch = s[0];
while (i < s.length())
{
switch (ch)
{
case '(':
tree.push(p);
break;
case ')':
tree.pop();
break;
case ',':
break;
default:
p = new Node();
p->val = ch;
if (root == NULL) //没有加入根节点
{
root = p;
}
else
{
tree.top()->children.push_back(p);
}
}
ch = s[++i];
}
}
我觉得这个代码逻辑非常清晰,我想尝试一下用递归的方式完成上述代码。在此之前,我先介绍一下我现在对于构建二叉树的理解,我感觉这个难度不亚于用非递归写出树的前中后序遍历😋
个人见解:
对于S字符串分析,S=A(B(,D(G,)),C(E,F(H,)))。从头开始遍历,想要通过S字符串构建一颗树。
想要将问题给解决,必须找到字符串内在的联系并且将其具体化。发现,所有的括号都是配对出现的,且’(‘其配对的是最近的’)’。每次当遇到’(‘的时候,可以把新的问题具体成”构建一棵小树“(发现和最开始的问题是一个内在逻辑)。然后小树挂在大树身上
解释一下:
最开始的问题是构建A(B(,D(G,)),C(E,F(H,))) 这棵树。
遍历到B后面的’(‘的时候,问题变成构建B(,D(G,)),C(E,F(H,))这棵树,然后B挂在A身上
遍历到D后面的’(‘的时候,问题变成构建D(G,)这颗树。然后D挂在B身上
发现了实际上就是解决无数个这样的小问题最后解决大问题,而进入小问题的接口就是遇到’('。所以只需要能解决最简单的A(B,C)这个问题,那么后面无限的衍生都是一个道理。
问题即抽象为:
A
(
B
,
C
,
D
,
E
.
.
.
.
)
A(B,C,D,E....)
A(B,C,D,E....)
即将每个子树都‘挂’在A后面即可
Node* CreateTree1(int i,int len) //表示创建一颗树(从下标i(根节点)开始,后面跟着长度为len的子树)即用s[i]~s[i+len]构造树
{
if (s[i] == ',')return NULL; //树的根应该是一个字母
Node* p = new Node();
p->val = s[i];
if (len == 0)return p;
//接下来要把该根节点的所有子树都挂在它身上
//要创建若干颗树
//子树都是由字符串s构造的
int start = i + 2; //子树开始位置
int length = 0; //子树(除去根节点)长度(从根节点后面第一个左括号到匹配完所有对应的右括号为止)比如A(B(C,D),E),这里面B(C,D)就是一个子树,子树长度在这里指根节点后面跟着的长度+1,即(C,D)的长度为5,B(C,D)的长度是6
while (start <= i+len)// 子树的区间[i+2,i+len],s[i+2]是子树的根节点
{
if (s[start] < 'A'|| s[start]>'Z') //保证s[start]是个字母(树的根节点)
{
start++;
continue;
}
length = Find_BabyTree(start); //找子树长度
p->children.push_back(CreateTree1(start, length)); //子树挂到根节点后面
start = start + length + 2; //下一个子树的起点
}
return p;
}
我的测试数据A(B(,D(G,)),C(E,F(H,)))
输出是正确的,就是博客开头的树
放完整代码
#include<iostream>
#include<vector>
#include<cstring>
#include<new>
#include<stack>
using namespace std;
class Node
{
public:
int val;
vector<Node*> children; //每个Node节点包含一个children数组,数组元素是Node*类型,表示指向其它的节点
//三个构造函数
Node() {}
Node(int _val)
{
val = _val;
}
Node(int _val, vector<Node*> _children)
{
val = _val;
children = _children;
}
};
string s;
void CreateTree(Node*& root, string& s) //括号表示法构建树,非递归法
{
stack<Node*>tree;
Node* p = NULL;
int i = 0;
char ch = s[0];
while (i < s.length())
{
switch (ch)
{
case '(':
tree.push(p);
break;
case ')':
tree.pop();
break;
case ',':
break;
default:
p = new Node();
p->val = ch;
if (root == NULL) //没有加入根节点
{
root = p;
}
else
{
tree.top()->children.push_back(p);
}
}
ch = s[++i];
}
}
void preOrdered(Node* root) //前序遍历,非递归法
{
if (root == NULL)return;
cout << char(root->val) << " ";
for (int i = 0; i < root->children.size(); i++)
preOrdered(root->children[i]);
return;
}
int Find_BabyTree(int start) //数以s[start]为根节点的子树长度
{
//根据括号规律,一个'('匹配一个')',当'('的次数等于')'的次数时表示当前这颗树构造完了
//左括号+1,右括号-1
if (s[start + 1] !='(')return 0; //该树只有一个根节点,子树长度为0
int kuohao = 0;
int i = 1;
for (i; i + start < s.length(); i++)
{
if (s[start+i] == '(')kuohao++;
if (s[start+i] == ')')kuohao--;
if (kuohao == 0)break;
}
return i;
}
Node* CreateTree1(int i,int len) //表示创建一颗树(从下标i(根节点)开始,后面跟着长度为len的子树)即用s[i]~s[i+len]构造树
{
if (s[i] == ',')return NULL; //树的根应该是一个字母个
Node* p = new Node();
p->val = s[i];
if (len == 0)return p;
//接下来要把该根节点的所有子树都挂在它身上
//要创建若干颗树
//子树都是由字符串s构造的
int start = i + 2; //子树开始位置
int length = 0; //子树长度(从根节点后面第一个左括号到匹配完所有对应的右括号为止)比如A(B(C,D),E),这里面B(C,D)就是一个子树,子树长度在这里指根节点后面跟着的长度,即(B,C)的长度为5
while (start <= i+len)// 子树的区间[i+2,i+len],s[i+2]是子树的根节点
{
if (s[start] < 'A'|| s[start]>'Z') //保证s[start]是个字母(树的根节点)
{
start++;
continue;
}
length = Find_BabyTree(start); //找子树长度
p->children.push_back(CreateTree1(start, length)); //子树挂到根节点后面
start = start + length + 2; //下一个子树的起点
}
return p;
}
int main()
{
Node* root = NULL; //根节点
s = "A(B(,D(G,)),C(E,F(H,)))"; //使用括号表示法构建树
//CreateTree(root, s); //建树
root = CreateTree1(0,s.length()-1);
//然后用前序遍历输出树看是否正确
preOrdered(root); //前序遍历
return 0;
}
强迫症使然,找到思考大致的题目:leetcode
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution
{
public:
TreeNode* CreateTree(string s) //表示当前根节点是s[i],当前的树的长度是len(包括i)
{
if(!s.size())
return NULL;
TreeNode*root=new TreeNode();
int flag=1;
int i=0;
if(s[i]=='-') //特殊判断负数
{
flag=-flag;
i++;
}
//要将截至到最近的'('的所有字符提取出来转换为数字(有可能不是一个字符)
int st=i;
int n=s.size();
while(i<n&&s[i]!='(')
i++;
root->val=flag*stoi(s.substr(st,i));
if(i==n)return root; //只有数字没有括号的情况,可以提前退出
//处理子树
int kuohao=1; //此时i指向左括号,左括号数量加1
int ls=i+1;
i++;
for(;i<n&&kuohao!=0;i++)
{
if(s[i]=='(')
kuohao++;
if(s[i]==')')
kuohao--;
}
//此时k指向左子树结束的后一个字符
root->left=str2tree(s.substr(ls,i-ls-1));
//看是否有右子树
if(i<n)
{
root->right=str2tree(s.substr(i+1,n-i-2)); //有点难算,先画个图
}
return root;
}
TreeNode* str2tree(string s)
{
//分解问题,找到规律:
//遇到字符则新建节点(当成一颗新树)
//将当前字符为根节点的左右子树挂到根节点后面(左右子树)
TreeNode *root=NULL;
root=CreateTree(s);
return root;
}
};