二叉树层次遍历(废话很多篇)

平时写代码没有时间限制,一笔试就各种手忙脚乱,昨天去哪儿笔试的时候也这样,写得乱七八糟的,二叉树遍历都能整出各种幺蛾子,最短路径忘了也就算了,这里各种遗漏真是不能忍啊。

首先是题目:

**按层打印二叉树 **

  • 时间限制:C/C++语言 2000MS;其他语言 4000MS
  • 内存限制:C/C++语言 65536KB;其他语言 589824KB
  • 题目描述:
    给定一棵二叉树的前序(根、左、右)和中序(左、根、右)的打印结果,输出此二叉树按层(从左往右)打印结果。
    例如一棵二叉树前序:1 2 4 5 3;中序:4 2 5 1 3。按层打印的结果则为:1 2 3 4 5。
  • 输入
    第一行只有一个数字,表示二叉树的节点数n(1<=n<=1000);
    第二行由a1,a2,…,an(1<=ai<=1000)组成的整数序列(用空格分隔)—表示前序打印结果;
    第三行由b1,b2,…,bn(1<=bi<=1000)组成的整数序列(用空格分隔)—表示中序打印结果。
  • 输出
    c1,c2,…,cn,用空格分隔—表示按层打印的结果。
  • 样例输入
    5
    1 2 4 5 3
    4 2 5 1 3
  • 样例输出
    1 2 3 4 5

先贴一遍提交上去的智障代码:

(function f(sum,po,mo){
    po = po.split(' ');
    mo = mo.split(' ');
    var result = {'level_sum':0},
    tree_stack = [{
        'l':1,
        'm':mo
    }],//[level,model]
    r_index,i,j;
    console.log(tree_stack);
    while(sum>0){
        tree = tree_stack.pop();
        console.log(tree);
        level = tree.l;
        model = tree.m;
        result.level_sum = level;
        if(!result[level]){
            result[level] = [];
        }
        r_index=po.indexOf(model[0]);//错误之一
        for(i=1;i<model.length;i++){
            if(po.indexOf(model[i])<r_index){//节点关键值相同时会失效
                r_index=i;
            };
        }
        result[level].push(model[r_index]);
        sum--;
        if(r_index>0){
            //tree_stack.push([level+1,model.splice(0,r_index)]);
        }
        if(model.length>r_index){
            //tree_stack.push([level+1,model.splice(r_index+1,mo.length)]);
        }
    }
    var arr = [];
    for(i=0;i<result.level_sum;i++){
        var tmp = result[i+1];
        for(j=0;j<tmp.length;j++){
            arr.push(tmp[j]);
        }
    }
    console.log(arr.join(' '));
})('5','1 2 4 5 3','4 2 5 1 3');

这里为方便本地测试修改了输入输出,原始代码调用的笔试系统提供的read_line()函数按行读取输入,然后print输出结果,发现一开始甚至连输入的节点总数是字符串都没有注意,直接当成数字使用了,虽然可能会自动强制类型转换,但是parseInt一下还是比较保险的?这里是后面加的,典型马后炮。

看题目想了半天,思路有点朦胧,然后跳着先把第二题的进制转换做了,第三题最短路径好久没看了,然后回来继续怼的第一题,说下最后的解决方案:根据前序和中序的特点,可以使用前序遍历中先遍历根的特点确认二叉树的根节点,然后在中序遍历中根据找到的根节点区分左右子树,然后就好办了,可以为初始的二叉树维护层数1,然后寻找其左右子树,则子树对应层数为其层数+1,利用栈可以很简单地实现,这样循环到最后就得到了所有节点所属层次的信息。当然前提这些二叉树中不能有关键值相同的节点,如果有的话,我暂时没办法解决。(此处应有翻白眼)然后,我就开始坑自己了。

接下来开始用心感受坑:

首坑,写代码的时候有点紧张,然后中间有一个细节弄混淆了,代码提交调试显示通过了20%的测试结果,自己感觉基本思路没问题,虽然有点简单粗暴,很多地方没细想,但应该问题不大。于是想当然地注释掉两个压入左右子树的代码,想单纯检查一下处理流程有没有什么逻辑问题,就这样把自己带进沟里,前面写完第二题,看了会第三题以后,回到第一题其实还有50分钟左右的时间,但是我在这里坑了差不多半个小时,最后就忙着干着急,在只剩5分钟的时候放弃挣扎提交代码了。(废话真多)

总之来看下我遇到了啥:

《二叉树层次遍历(废话很多篇)》
《二叉树层次遍历(废话很多篇)》

这边看到TypeError很是懵逼啊,我还特意把tree打印出来看看。可是上一句刚刚打印完,下一句怎么就翻脸不认tree啦?怎么就undefined了?引擎你的良心不会痛吗?

接着我的脑子就月球漫步去了,我就开始乱改代码,改一下tree_stack里面用的数据类型啊,从一开始使用的[level,model](一个包含两个成语的数组,第一个成员记录层数,第二个记录一个子树的所有节点)变成{level:n,model:Array[n]}(一个包含level和model属性的对象);但是这种没有根据的瞎改当然不会有什么结果,我就改一遍在chrome里测试一遍,没用,在笔试系统里面测试一遍,没用…..直到世界终结。然后代码交上去以后我就开天眼了,世界级马后炮选手的名号不是随便得来的。

我把出错代码在内的地方全部注释了一遍,然后…….没什么发现,但是重新加上去的时候我看到了一开始最先加的注释,就是注释掉两个子树入栈的操作,这边有没有可能,其实是循环出口设置的极其简单粗暴,注释掉子树入栈以后循环并不会结束,出栈操作在栈空的时候不会有提示,只是会默默返回undefined,然后我看到的TypeError其实是第二遍循环中遭遇的。遂测试之。

啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊…..

一个教训就是,不能贪快,越是着急越容易搞出事情来。

好了那么首坑就算是迈过去了,我们继续深入,啊不,继续前进。

仔细看一眼代码,很多变量我都没有声明直接使用了,然后就变成全局变量了,这样很容易给自己测试的时候埋坑,比如:

《二叉树层次遍历(废话很多篇)》 未声明变量变成全局变量

然后来看原始代码的输出结果:

《二叉树层次遍历(废话很多篇)》 奇妙的输出结果

这边说下我在找到利用栈对二叉树及其所有左右子树执行相同操作,每次找到一颗树(无论原始二叉树还是它的子树,下文中所有树均代表这两者之一)的根节点,都将该节点结合其层次信息,维护到一个result对象上,result对象拥有一个level_sum属性,记录二叉树总层数,还有若干数字属性,每个数字对应相应层数,里面维护了该层次所有节点,其实就是一个数组,每次找到对应层的节点就压入到对应层次数组中就好了。(这么奇葩的设定纯粹是因为懒得考虑其他结构)

这里观察上图会发现,result对象中维护了五个层次数组。哪里不对的样子?最后三层还都是只有一个undefined成员的奇形数组。喵喵喵?这里分析一下测试输入,正确结构应该是这个样子的呀?

《二叉树层次遍历(废话很多篇)》 测试输入对应的二叉树结构

为了方便观察运行状况添加了几个console.log分别打印每次循环中使用的树的model(即其节点集合),找到的根节点以及压入栈的左右子树,然后观察一下运行结果:

《二叉树层次遍历(废话很多篇)》 添加console.log的测试结果

可以看到“好像”除了一开始找到了两个“正确”的根节点,之后全都是indexOf找不到对应成员返回的-1和undefined,也就是说之后根节点都没找对,还有一开始只找到了左子树,提都没提右子树,接下来更离奇找到一个空的(这里应该是undefined…..吧?)的右子树。

先来看一下寻找根节点的代码:

r_index=po.indexOf(model[0]);//错误之一
for(i=1;i<model.length;i++){
  if(po.indexOf(model[i])<r_index){//节点关键值相同时会失效
    r_index=i;
  };
}
result[level].push(model[r_index]);

说下思路,model中包含了其对应树的所有节点元素(的关键值),这里在(我假设的)各节点元素唯一的前提下,遍历该树所有节点并确认其在前序遍历中出现的顺序,因为前序总是先访问根节点,所以在前序遍历出现顺序最早的节点,就是该树的根节点。这里使用的是indexOf检查数组成员的方法实现的,简单粗暴,还只对基本类型有效,无法检测NaN,不过那些有点扯远了,先考虑最起码的实现。

乍一看,我好想说的很有道理,但其实写出来的代码漏洞不要太多:

首先,上述代码中混淆了节点在前序遍历中的索引号和树model中的索引号,r_index最开始保存的是树的首个节点成员在前序遍历中出现的索引,然后当发现索引小于该值的成员后,r_index又转过来存储了i的值,即该节点在树model中的索引,这里这个错误简直不要太搞笑,所以其实很好奇测试数据里面输入了什么神奇的代码让我还命中了20%的答案。(扶额)

理一下逻辑,修改后的代码应该长这样:

p_min=po.indexOf(model[0]);//保存树节点在前序遍历中出现最小索引
r_index=0;//保存树节点在model中的索引
for(i=1;i<model.length;i++){
    var tmp = po.indexOf(model[i]);//这里tmp的声明是不是该挪到上面我也很纠结
    if(tmp<p_min){//节点关键值相同indexOf会返回第一个出现的index,有相同的话(;´༎ຶД༎ຶ`) 
        p_min=tmp;
        r_index=i;
    };
}
result[level].push(model[r_index]);//将找到的根节点维护到对应的层次数组上去

修改完的测试结果:

《二叉树层次遍历(废话很多篇)》 看上去仍然很沉重啊

可以看到右子树依然很倔强地保持空白,不过最起码结果对象少了一层。恩,有进步有进步。

那来看看压入子树的代码吧:

if(r_index>0){
    var subtree_left = model.splice(0,r_index);
    console.log('got subtree for left : '+subtree_left);
    tree_stack.push([level+1,subtree_left]);
}
if(model.length>r_index){
    var subtree_right = model.splice(r_index+1,mo.length);
    console.log('got subtree for right : '+subtree_right);
    tree_stack.push([level+1,subtree_right]);
}

这里是想着,我们找到了根节点,那根据树的model(其实就是中序遍历的片段),很容易找到左右子树了吧,大概结构不就是:

                       [{左子树的所有节点},root,{右子树的所有节点}]

没毛病啊简直,诶,等下,这个splice有点辣眼睛啊?splice方法似乎会直接把原始数组“切碎”吧?所以,切掉左子树以后model就直接瘫痪了,然后右子树就人间蒸发了,因为我写的智障设定的索引值里找不到正确的右子树成员啊!啊啊啊啊!

考虑splice对model的影响,修改后:

if(r_index>0){
    var subtree_left = model.splice(0,r_index);
    console.log('got subtree for left : '+subtree_left);
    tree_stack.push([level+1,subtree_left]);
}
if(model.length>1){
    var subtree_right = model.splice(1,mo.length);
    console.log('got subtree for right : '+subtree_right);
    tree_stack.push([level+1,subtree_right]);
}

测试结果:

《二叉树层次遍历(废话很多篇)》 哈哈哈哈哈哈?哈?

我果然是天才吧?啊哈哈哈哈哈?哈哈哈?让我写出来了吧?哈哈哈?哈?!!???

友情提示,请看题目:

….
给定一棵二叉树的前序(根、左、右)和中序(左、根、右)的打印结果,输出此二叉树按层(<u>从左往右</u>)打印结果。
….

  • 样例输入
    5
    1 2 4 5 3
    4 2 5 1 3
  • 样例输出
    <u>1 2 3 4 5</u>

再看看你的输出:

《二叉树层次遍历(废话很多篇)》 扎心了老年痴呆

说了多少遍栈是LIFO,LIFO你知道不?哈?上天吧?年轻人?啊啊啊啊啊?
上天之前改的代码:

if(model.length>r_index+1){
    var subtree_right = model.splice(r_index+1,mo.length);
    console.log('got subtree for right : '+subtree_right);//注意左右子树的压入顺序会影响层次遍历结果,即左到右或右到左;
    tree_stack.push([level+1,subtree_right]);
}
if(r_index>0){
    var subtree_left = model.splice(0,r_index);
    console.log('got subtree for left : '+subtree_left);
    tree_stack.push([level+1,subtree_left]);
}

测试结果:

《二叉树层次遍历(废话很多篇)》 ???

卧槽,上天前就不能让我消停会么?level_sum怎么变成2了?什么鬼啊?我是不是真的不适合当程序员?

level_sum相关片段:

tree = tree_stack.pop();
level = tree[0];
model = tree[1];
console.log('now processing tree with model: '+model);
result.level_sum = level;

为了方便观察我把console.log修改了一下:

console.log('now processing tree with model: '+model+' on level: '+level);

测试结果:

《二叉树层次遍历(废话很多篇)》 !!!

哈哈哈哈哈哈哈哈….哈….(断气)

断气前改的代码:

result.level_sum=Math.max(result.level_sum,level);//略简单粗暴

《二叉树层次遍历(废话很多篇)》 真·测试结果

好了,年轻人,三思而后行啊!啊?(谢幕…..)

我肥来了,复习了一遍书里的层次遍历,感觉自己要蠢哭了,根本不需要辅助的数据结构,直接用队列就搞定:树入队,找根结点,输出根节点,左右子树入队,反复进行直到结束。

    原文作者:假装会编程
    原文地址: https://www.jianshu.com/p/7581a7920646
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞