原题链接-洛谷
代码是模仿lrj大神的,在这里主要写一些自己的理解和分析,希望可以帮助大家理解这道题。
本题的最大节点数为256,且并未指出最大深度,所以如果用编号思想的话,要编号到2的256次方,我们都知道这是个天文数字,即使用高精度也是不好做的。而且我们完全没有必要用到编号,因为即使编号了,这课树的绝大多数编号也都是没有被使用的,(最坏情况下是大概是2的256次方-256个未使用),所以可以说这棵树是很稀疏的一棵树。
那么我们就只能用另外一种方法了,就是用几个节点就申请多大的空间。更好的事情是,我们可以在最后释放这些空间。
以下是代码实现过程
一、分析题目的输入组数
我们发现可能出现多组数据输入输出的情况,遇到这种情况时,我们的主函数模板一般是这样的:
情况一(没有事先给出输入组数的时候):
while(输入函数返回值为TRUE){
将答案保存起来,然后输出,或者直接输出。
}
这里的输入函数可能是scanf语句或者cin,比如有些题会说明以-1为结束,可以这样写
while(scanf("%d",&k)==1 && k!=-1);
也可能是m,n两个,同样道理。
这个输入函数也可以是根据具体题目情况自定义的函数,比如这道题我采用的就是自定义函数。
情况二(给出输入组数):
这个比较简单,可以先输入组数t,然后while(t --){}即可。
二、分析基本解题思路
1、因为不能编号,所以我们采用自定义的结构体链接整棵树。
2、输入方式我们用自定义的输入函数来解决。
3、先建树,再bfs。
4、很多同学可能思考到这一步就比较凌乱,不知道之后该怎么做,这时我推荐我们可以一开始不要思考太多,可以先开始做题,然后慢慢发现问题,慢慢把代码写出来。
三、直接先能写多少写多少,先写出主函数的框架。
下面是伪代码:
int main(){
//申请一个数组用来存答案;
while(自己写的读入数据的函数(),读入同时就可以建树){
bfs;
输出数据;
}
return 0;
}
四、进一步思考,如何来读取数据
这时看输入样例,我们发现所有的插入的结点之间都是空格隔开的,这时推荐用scanf来写,并且每一组都有唯一的结束标志(),所有输入彻底结束的标志是“没有标志”,也就是没有任何数据继续读入时,读入数据要立即结束。也可以理解为scanf返回值非1(就是读不到东西了)所以框架继续搭建。
char s[1001005];
bool read_tree(void){
while(true){
if(scanf("%s",s)!=1) return false;//如果读不到东西,返回false,这样主函数的循环就结束了
if(!strcmp(s,"()")) break;//如果遇到括号,只代表这一组数据结束。
解析输入格式并处理数据...
}
return true;
}
这样就能把数据读进来了,虽然只是存在字符数组中,还没有进行解析,但是我们这个时候可以把我们读到的东西试着原样输出一下,调试一下有没有问题,对于这种比较复杂的程序,逐个调试子函数是很有必要的,这样可以尽量避免写完所有之后,找半天不知道到底哪个地方有错。
五、对于读入的数据进行解析
我们之前只把数据读在了字符数组里面,但是这道题的目标是把依靠数据建树,所以我们要进行解析。首先我们可以很直观地看到我们读的数据分为两个部分,以输入样例为例子,我们读进来的第一个s的内容是(11,LL),它的两个部分是11 和 LL 括号和逗号都是我们不要的。
所以我们现在来解析s数组。
1、解析出新节点的权值: 这时我们要先学习一个新的知识,sscanf函数,我的理解是,它的作用是将其第一个参数当做用户输入的内容来进行输入,后两个参数和我们熟悉的scanf函数的两个参数无异。
以(11,LL)为例,11是我们要加的点的权值,我们定义一个变量叫做val来存它(value的简写),然后请读者仔细理解这个语句:
sscanf(&s[1],"%d",&val);
其实就相当于我们用scanf语句读入数据时,用户写在那个黑框里面敲的是11,LL) 一样。这个语句读了11,舍弃了其它的,因为其它不是整型数据。有同学会问,为什么少了个括号。注意看这一句里面是s[1],代表是从s的第一个而不是第0个字符开始的。另外要记得加取地址符号。
2、接下来要解决的是解析出位置,就是LL部分,我们又要学习一个新知识,strchr函数,它有两个参数,第一个是字符串,第二个是字符,所以它的名字很好记,string和char的结合,strchr,作用是返回指定字符串中第一个该字符的地址。注意这个表达式:
strchr(s,’,’)+1
这个运算结果会返回s字符串中逗号地址再加一,也就是(11,LL)例子中的第一个L的地址。
有了这个地址,我们就能找到s字符串中逗号之后的内容了,至此我们就完成了对读入数据的解析。接下来我们进一步完善我们的读入数据函数。
六、完善读入数据函数:
bool read_tree(void){
failed=false;
while(true){
if(scanf("%s",s)!=1) return false;
if(!strcmp(s,"()")) break;
int val;
sscanf(&s[1],"%d",&val);
add_node(val,strchr(s,',')+1);
}
return true;
}
我们把解析出的两个数据作为参数传递给了名叫add_node的函数,这个函数将帮助我们给树里面添加节点,这时我们假设已经写出了这个函数,先把这句写在这里,等一会再完成这个函数。
但是还要注意这道题里的一个坑,就是题目之中读入的数据可能是非法的,这个时候需要我们进行判断,我们定义变量failed为false代表合法,failed为true代表非法,我们每次刚开始读入时先假设它是合法的,之后failed的值可能会改变,这个要根据具体这道题的非法条件来看,我们暂时不管这个,先把failed赋值为false;
七、有了节点的两个参数(权和位置),就可以写addnode函数了
在写addnode之前,先把节点用结构体定义一下
struct Node{
bool have_value;//因为题给数据可能存在重复给值的情况,所以要记录一下节点是否已经有值,如果已经有值还要
//给值的话,就直接让failed赋值为true
int val;
Node *lefti,*righti;
Node():have_value(false),lefti(NULL),righti(NULL){}//构造函数,用来赋初值
};
我们之前分析过,由于树可能很“稀疏”,所以空间是动态申请的,这里写一个申请节点内存空间的函数
Node* new_node(){
return new Node();
}
再把根节点先申请出来
Node* root;
现在开始写addnode
该函数的两个参数分别是值和位置,我们只要根据描述位置的字符串找到相应位置,如果相应位置上没有被赋值,就给它赋相应值。
先写伪代码
void add_node(int v,char* s){
int n=strlen(s);
Node* u=root;//从根节点开始找起
int i;
for(i=0;i<n;i++){
如果是L就向该节点左子树找,R就向右找,如果要找的方向暂时没有申请过节点,就先申请出来,但不一定赋值。
比如,如果在只有根节点的情况下,但是要加入的节点在LLL位置,这时中间是断开的,那我们就直接在这一步将根
节点左子节点L,左子节点的左子节点LL都申请出来,但是这两个先不赋值,也不标记已经赋值,只给LLL赋值。
}
if(u->have_value) failed=true;//这三句是判断是否已经有值
u->val=v;
u->have_value=true;//注意赋完值之后标记一下
}
分析清楚之后,完成addnode的完整代码
void add_node(int v,char* s){
int n=strlen(s);
Node* u=root;
int i;
for(i=0;i<n;i++){
if(s[i]=='L'){
if(u->lefti == NULL) u->lefti = new_node();
u=u->lefti;
}
else if(s[i]=='R'){
if(u->righti == NULL) u->righti =new_node();
u=u->righti;
}
}
if(u->have_value) failed=true;
u->val=v;
u->have_value=true;
}
这样就完成了建树的代码了,之后我们就可以通过根节点找到找到任意一个子节点了。
八、接下来一步就是用bfs对整棵树进行遍历,bfs过程就不用多说了,这个比较简单。
bool bfs(vector<int>& da_an){
queue<Node*> q;
da_an.clear();
q.push(root);
while(!q.empty()){
Node* u=q.front();
q.pop();
if(!u->have_value) return false;//注意这里,如果存在没有赋值的节点,那就说明树从中间“断”掉了,那就是非法的。
da_an.push_back(u->val);
if(u->lefti != NULL) q.push(u->lefti);
if(u->righti != NULL) q.push(u->righti);
}
return true;
}
九、下面贴出整个代码供大家参考
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1001005;
//pre_define
//define Node
struct Node{
bool have_value;
int val;
Node *lefti,*righti;
Node():have_value(false),lefti(NULL),righti(NULL){}
};
//define var
bool failed;
Node* root;
char s[maxn];
//pre_var
Node* new_node(){
return new Node();
}
void remove_tree(Node *u){//这个是递归释放空间的过程
if(u==NULL) return;
remove_tree(u->lefti);
remove_tree(u->righti);
delete u;
}
void add_node(int v,char* s){
int n=strlen(s);
Node* u=root;
int i;
for(i=0;i<n;i++){
if(s[i]=='L'){
if(u->lefti == NULL) u->lefti = new_node();
u=u->lefti;
}
else if(s[i]=='R'){
if(u->righti == NULL) u->righti =new_node();
u=u->righti;
}
//if(u->have_value) ok=true;
//u->val=v;
//u->have_value=true;
}
if(u->have_value) failed=true;
u->val=v;
u->have_value=true;
}
bool bfs(vector<int>& da_an){
queue<Node*> q;
da_an.clear();
q.push(root);
while(!q.empty()){
Node* u=q.front();
q.pop();
if(!u->have_value) return false;
da_an.push_back(u->val);
if(u->lefti != NULL) q.push(u->lefti);
if(u->righti != NULL) q.push(u->righti);
}
return true;
}
//jie_xi_biao_da_shi;
bool read_tree(void){
failed=false;
remove_tree(root);
root=new_node();
while(true){
if(scanf("%s",s)!=1) return false;
if(!strcmp(s,"()")) break;
int val;
sscanf(&s[1],"%d",&val);
add_node(val,strchr(s,',')+1);
}
return true;
}
int main(){
vector<int> da_an;
while(read_tree()){
if(failed || !bfs(da_an)) cout<<"not complete"<<endl;
else{
for(vector<int> :: iterator it=da_an.begin(); it != da_an.end();it++){
cout<<*it;//在这里用到了stl的迭代器,这种用法紫书前面的题有讲解过。
if(it!=da_an.end()-1) cout<<' ';
}
cout<<endl;
}
}
return 0;
}