V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
woai110120130
V2EX  ›  程序员

昨天做了个二叉树的笔试题 大概十几分钟就做完了 但是有些疑惑 想到了一个新题 不知道有没有人能解

  •  
  •   woai110120130 · 2015-06-15 22:13:39 +08:00 · 6382 次点击
    这是一个创建于 3467 天前的主题,其中的信息可能已经有所发展或是发生改变。
    下面是笔试题目: 根据用户的输入的字母, 构建一个满二叉树, 然后打印出它的所有从根到叶子的路径. (假设用户的输入字母的个数恰好满足构建一个满二叉树)
    例如: 输入 a b c d e f g后构建成下面的树
    a
    b c
    c d e f 就是这样的一个满二叉树

    然后输出 abc,abd,ace,acf
    要求: 使用任意一种你熟悉的语言即可。
    然后菜鸟我是这样做的

    /**
    * Created by tangtang on 15/6/15.
    */
    public class Tree {


    public static void main(String[] args)
    {
    byte[] byteArray={'D','B','A','C','F','E','G'};

    Node root=new Node();




    for (byte b: byteArray)
    {
    root.insert(b);

    }

    root.traverseBiTree("");
    }


    static class Node{

    private byte value=-1;

    private Node leftChild;
    private Node rightChild;

    public Node getLeftChild()
    {
    return leftChild;
    }

    public Node getRightChild()
    {
    return rightChild;
    }


    public byte getValue()
    {
    return value;
    }

    public void setLeftChild(Node leftChild)
    {
    this.leftChild=leftChild;
    }

    public void setRightChild(Node rightChild)
    {
    this.rightChild=rightChild;
    }

    public void setValue(byte value)
    {
    this.value=value;
    }


    /**
    * 遍历加打印 遍历路径
    * 思路 让上一层都产生一个string
    * @return
    */
    public void traverseBiTree(String str)
    {
    str+=(char)value;
    if (leftChild==null&&rightChild==null) {
    System.out.println(str);
    return;
    }

    if (leftChild!=null)
    {
    leftChild.traverseBiTree(str);
    }

    if (rightChild!=null)
    {
    rightChild.traverseBiTree(str);
    }
    }




    public void insert(byte c)
    {
    if (value==-1) {
    value = c;
    return;
    }

    if (c>value)
    {
    if (rightChild==null)
    rightChild=new Node();
    rightChild.insert(c);
    }else {
    if (leftChild==null)
    leftChild=new Node();
    leftChild.insert(c);
    }

    }

    }
    }

    用java写的 有点长 不过去掉set get方法也非常简洁了


    在做的过程产生了这样一个问题
    如果 试题问的 就是输入a b c d e f g 生成上边的那个满二叉树 要求下一层的孩子都大于上一层的孩子 并且 右孩子都大于左孩子 如何生成该 上面的那个满二叉树? 好好想想 这个问题还是很困难的
    要求只能一个一个的插入 不可以先排序再生成二叉树
    56 条回复    2015-06-16 22:16:33 +08:00
    Heartwork
        1
    Heartwork  
       2015-06-15 22:33:02 +08:00 via Android
    听说过平衡二叉树或者红黑树么?它们在插入过程当中通过自旋就能满足你说的要求。
    woai110120130
        2
    woai110120130  
    OP
       2015-06-15 22:37:36 +08:00
    @Heartwork 这位兄台 你实现一个看看 平衡二叉树 并不能满足需求 不信你做做看
    yangff
        3
    yangff  
       2015-06-15 22:44:16 +08:00 via Android
    treap
    woai110120130
        4
    woai110120130  
    OP
       2015-06-15 22:57:02 +08:00
    @yangff 1.如果v是u的左孩子,则key[v] < key[u].
    2.如果v是u的右孩子,则key[v] > key[u].
    3.如果v是u的孩子,则priority[u] > priority[u]. 这是树堆的特点 好像并不满足
    Heartwork
        5
    Heartwork  
       2015-06-15 23:01:36 +08:00
    @Heartwork 恩,果然不行。

    树的底层使用数组表示,插入算法使用普通的数组插入,可以保证最后的树为符合条件的完全树。

    不过整体算法复杂度为O(n^2)。

    你有更好复杂度的算法没?
    yangff
        6
    yangff  
       2015-06-15 23:03:49 +08:00 via Android
    @woai110120130 priority=key
    woai110120130
        7
    woai110120130  
    OP
       2015-06-15 23:04:27 +08:00
    @Heartwork 嗯 我并没有解出来 想的不错 但是一写发现还是有问题
    binux
        8
    binux  
       2015-06-15 23:05:00 +08:00
    #!/usr/bin/python
    #如果你会用数组表示二叉树,就会知道这个问题有多简单

    l = 'abcdefg'

    for i in range(len(l)/2+1, len(l)+1):
    __result = []
    __while i:
    ____result.append(l[i-1])
    ____i /= 2
    __result.reverse()
    __print ''.join(result)
    woai110120130
        9
    woai110120130  
    OP
       2015-06-15 23:09:18 +08:00
    @binux 你这个并不对 如果打乱abcdefg的顺序呢 比如插入 t c b d a f k呢
    binux
        10
    binux  
       2015-06-15 23:10:22 +08:00
    @woai110120130 你只要求是满二叉树啊,又没规定是怎么插入的
    woai110120130
        11
    woai110120130  
    OP
       2015-06-15 23:11:21 +08:00
    看看最后那段话 问题在最后
    binux
        12
    binux  
       2015-06-15 23:17:26 +08:00   ❤️ 1
    @woai110120130 谁去看最后啊,那不就是个小根堆啊
    Heartwork
        13
    Heartwork  
       2015-06-15 23:19:25 +08:00
    @binux

    英雄所见略同:)

    顺序插入的话,直接把元素放到末尾就好。

    不过考虑如果数据为随机插入的话,整个算法就退化为有序数组的插入算法了。

    整个过程还是O(log n^2)
    Heartwork
        14
    Heartwork  
       2015-06-15 23:22:40 +08:00
    @binux

    不是堆,堆的性质只能保证parent的value比child小。
    wodesuck
        15
    wodesuck  
       2015-06-15 23:23:43 +08:00 via Android
    @binux +1 不就是个小根堆嘛
    binux
        16
    binux  
       2015-06-15 23:24:42 +08:00
    @Heartwork 既然对于小根堆来说,左右孩子大小关系没有影响,你交换一下不就好了。。。
    wodesuck
        17
    wodesuck  
       2015-06-15 23:25:58 +08:00 via Android
    @Heartwork 二叉堆为什么有退化的问题。。不是严格O(nlogn)的吗。。
    zwzmzd
        18
    zwzmzd  
       2015-06-15 23:29:09 +08:00
    我觉得这个题目限制不严,比如说不能先排序,那如果之后的算法内比较再交换允许吗?一般的算法题,限制时间复杂度和空间复杂度,都是很明确的。

    提供个思路:用一维数组表示满二叉树,你可以顺序把元素全填进去,然后快排调整
    Heartwork
        19
    Heartwork  
       2015-06-15 23:29:17 +08:00 via Android
    @binux
    无法避免a的兄弟节点的孩子比a大的状况
    Heartwork
        20
    Heartwork  
       2015-06-15 23:30:12 +08:00 via Android
    @wodesuck
    不是,我的方案是使用有序数组。
    zwzmzd
        21
    zwzmzd  
       2015-06-15 23:33:43 +08:00
    再者,基于比较的排序算法已经证明了时间复杂度下界是O(nlogn)。你要的结果里面所有元素已经有序了,所以如果你的算法基于比较,也不可能超过这个下界。
    binux
        22
    binux  
       2015-06-15 23:36:12 +08:00
    @Heartwork 并不违反 「下一层的孩子都大于上一层的孩子 并且 右孩子都大于左孩子」吧,那有什么关系?
    zwzmzd
        23
    zwzmzd  
       2015-06-15 23:37:08 +08:00
    @binux
    @Heartwork
    只交换是不行的,但再加上向下调整就行了
    binux
        24
    binux  
       2015-06-15 23:40:46 +08:00
    @zwzmzd 为什么不行?首先小根堆保证了左右两个元素比父元素大,交换了依然满足这个条件啊。
    Heartwork
        25
    Heartwork  
       2015-06-15 23:45:26 +08:00 via Android
    @binux
    说反了,应该是 无法避免a的兄弟节点的孩子比a小的状况

    这样:

    a
    b z
    c d
    c和d是b的孩子,但是比z小。
    zwzmzd
        26
    zwzmzd  
       2015-06-15 23:46:28 +08:00
    @binux 就说一个简单的小根堆 1,5,2,6,7,3,4 (数组表示)
    交换中间层元素5,2之后,会变成 1,2,5,6,7,3,4,不符合小根堆性质了

    结论:已经调整完毕的小根堆,不可随意交换某两个孩子的位置。
    Heartwork
        27
    Heartwork  
       2015-06-15 23:48:50 +08:00 via Android
    有序的完全二叉树为一有序序列,在插入过程中插入位置之后的节点都需要向后移动,所以这个题目等同于求一个有序数组的插入算法…

    这样应该没问题了。
    woai110120130
        28
    woai110120130  
    OP
       2015-06-15 23:51:12 +08:00
    @zwzmzd 其实说不能先排序 是考虑到性能的问题 每次都要排序 会浪费极大的性能 其实这也不是真实中需要的算法了 只不过是在做题的过程中产生的思考罢了 看了楼上的 觉得还没有满意的答案 为什么没有人动手实现一下呢 想的永远回比做起来简单
    woai110120130
        29
    woai110120130  
    OP
       2015-06-15 23:54:52 +08:00
    @Heartwork bingo 其实这么做 把先排序换成了后排序 我在想有没有更好的办法
    Heartwork
        30
    Heartwork  
       2015-06-16 00:03:31 +08:00 via Android
    后排序会导致插入过程中树无法保持有序的条件,这个题目就退化为给一个数组排序了。

    底层如果使用二叉树的话,需要维护每一个元素的“前驱”和“后继”,前驱和后继指value的前一个和后一个。

    这样插入时需要遍历插入位置之后的元素,复杂度和数组的相同。
    binux
        31
    binux  
       2015-06-16 00:05:35 +08:00
    @Heartwork
    @zwzmzd 难道 「下一层的孩子都大于上一层的孩子」 这句话的意思是,大于上一层的所有节点?
    woai110120130
        32
    woai110120130  
    OP
       2015-06-16 00:07:31 +08:00
    @Heartwork 洗澡的时候想了想 这并没有什么卵用 排完序之后 还需要重新组织二叉树 还是违背题的
    Heartwork
        33
    Heartwork  
       2015-06-16 00:08:58 +08:00 via Android
    @binux

    我是这样理解的。就是一颗有序的完全二叉树。
    woai110120130
        34
    woai110120130  
    OP
       2015-06-16 00:09:35 +08:00
    @Heartwork 亲 你觉得可行 可以写出来让大家学习学习
    Heartwork
        35
    Heartwork  
       2015-06-16 00:09:54 +08:00 via Android
    @woai110120130
    所以底层用数组就行了
    binux
        36
    binux  
       2015-06-16 00:39:02 +08:00
    @Heartwork

    1、如果这么理解,这个题目是有问题的。
    既然第 n 层 全部大于 第 n-1 层,「右孩子都大于左孩子」没有任何意义,在同一层之间交换元素,没有任何影响和副作用。那么这个 「右孩子都大于左孩子」的要求,随便交换一下元素就可以了。

    2、其次,这个问题不是完全排序的。
    只要使用快速划分,让数组左边一半小于右边一半,然后对左边继续前面的过程。再加上1所诉的的交换元素,就可以建立起符合要求的树了。复杂度只有 O(n)
    woai110120130
        37
    woai110120130  
    OP
       2015-06-16 08:41:56 +08:00
    楼上的诸位说这么多只不过是纸上谈兵罢了 没有一个肯做出来用事实说话
    Heartwork
        38
    Heartwork  
       2015-06-16 08:45:08 +08:00 via Android
    @binux
    n层大于n-1层(a)与右孩子大于左孩子(b)是两个独立的条件,写两个例子。
    a成立b不成立:
    a
    c b
    b成立a不成立:
    b
    a c
    这两个条件共同约束下只能是有序序列。
    Heartwork
        39
    Heartwork  
       2015-06-16 08:48:00 +08:00 via Android
    @binux
    对了,你说的2的“调整”如果满足a和b,就是一个快速排序。
    binux
        40
    binux  
       2015-06-16 09:03:12 +08:00
    @woai110120130 事实?你自己问题都说不清楚,让人家找事实?你写得出测试用例吗?

    @Heartwork 不是的
    a
    b c
    d e f g
    成立

    a
    b c
    e g d f
    也成立

    同层之间,只要 2n 和 2n+1 做个交换就能满足条件 b
    Heartwork
        41
    Heartwork  
       2015-06-16 09:14:03 +08:00 via Android
    @binux 恩,不错,是这样
    binux
        42
    binux  
       2015-06-16 09:17:43 +08:00
    @Heartwork 所以只要层间排序,不需要层内排序。
    如果一次解决就是做一半快速划分,如果插入就是层内做大根堆。
    都是 O(n) 的
    Heartwork
        43
    Heartwork  
       2015-06-16 09:38:18 +08:00 via Android
    @binux 堆的那个恐怕不行吧。怎么处理插入一个比该层元素都小的值?
    binux
        44
    binux  
       2015-06-16 10:07:12 +08:00
    @Heartwork 大根堆顶往下推,不然为什么是大根堆呢
    zwzmzd
        45
    zwzmzd  
       2015-06-16 10:17:43 +08:00 via Android
    这个题目根据描述很可能有歧义,我觉得楼主你在叫人写代码之前应该给出几个测试用例,这样别人才好着手去写
    woai110120130
        46
    woai110120130  
    OP
       2015-06-16 12:03:59 +08:00
    @zwzmzd 哈哈 没有其他的意思 就是讨论学习 我并没有测试用例 确实有人理解的不太正确 嗯等晚上下班在写吧
    woai110120130
        47
    woai110120130  
    OP
       2015-06-16 12:05:12 +08:00
    @binux 不是我 我出题的意思是
    隐藏 感谢回复者 Reply 40
    binux 3 小时 0 分钟前
    @woai110120130 事实?你自己问题都说不清楚,让人家找事实?你写得出测试用例吗?

    @Heartwork 不是的
    a
    b c
    d e f g
    成立

    a
    b c
    e g d f 这个不成立 右边的都要大于左边的 可能是我描述的不清楚 实在不好意思哈
    binux
        48
    binux  
       2015-06-16 12:33:48 +08:00
    @woai110120130 如果右边的都要大于左边,这棵树可以被证明是完全排序的,这个题目就更没意义了。
    billwsy
        49
    billwsy  
       2015-06-16 14:43:42 +08:00
    你要求的所谓树用数组来表示那就是有序数组嘛,要求可以快速随机访问插入那就是块状链表了,效率是sqrt(n)
    woai110120130
        50
    woai110120130  
    OP
       2015-06-16 14:46:57 +08:00
    @billwsy 其实更像是杨辉三角 但是要求动态插入 而不是排好序再排列
    billwsy
        51
    billwsy  
       2015-06-16 14:52:26 +08:00
    Oops, 似乎弄错了,我把条件加强了。。
    billwsy
        52
    billwsy  
       2015-06-16 15:02:59 +08:00
    这样做如何:每一层对应一个大根堆,插入时二分查找到需要插入的层,O(lg lg n),将其根剔除并插入元素,将其根插入下一层,每次执行O(lg n),最多执行lg n次,效率O(lg^2 n);总效率O(lg lg n + lg n * lg n) = O(lg^2 n)。如果用数组来存储堆,并且将所有数组头尾相接起来,可以以大数组来解释二叉树。
    这样的话,总效率O(n lg^2 n),缺点是插入时会破坏所有原有的父子关系。
    billwsy
        53
    billwsy  
       2015-06-16 15:05:52 +08:00
    Oops,又漏了。用大数组来解释时,会出现左儿子大于右儿子的情况,访问时交换它们修正就可以了
    billwsy
        54
    billwsy  
       2015-06-16 15:07:48 +08:00
    发现@binux 已经给出正解了…默默匿了…
    billwsy
        55
    billwsy  
       2015-06-16 15:10:45 +08:00
    好吧,按@woai110120130 47楼的解释,那我的解答就是49楼提到的块状链表了……
    洗洗睡了……
    woai110120130
        56
    woai110120130  
    OP
       2015-06-16 22:16:33 +08:00
    @billwsy 好的吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2328 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 15:38 · PVG 23:38 · LAX 07:38 · JFK 10:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.