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

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

首先是题目:

**按层打印二叉树 **

  • 时间限制: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);//略简单粗暴
真·测试结果

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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,432评论 1 31
  • 数据结构和算法--二叉树的实现 几种二叉树 1、二叉树 和普通的树相比,二叉树有如下特点: 每个结点最多只有两棵子...
    sunhaiyu阅读 6,422评论 0 14
  • 关于树的定义和存储结构可以查看上一篇文章树的定义和树的三种存储结构 一、二叉树的定义 二叉树的定义 二叉树(Bin...
    NotFunGuy阅读 1,945评论 5 25
  • 扶风长天阅读 296评论 1 1
  • 下个月我孙女就两岁了,宝贝,奶奶时刻都在盼望,盼望你能早点回家。你爸爸把你的东西收拾得妥妥的,爸爸和爷爷奶奶都是你...
    寒江雪810阅读 112评论 0 0