二叉树旋转


16

平衡的二进制搜索树对于保证O(log n)查找(或类似操作)至关重要。在动态环境中,随机地插入和/或删除了许多密钥,树可能会退化为链接列表,这对于查找来说是可怕的。因此,存在多种抵消这种效果的自平衡二叉树(例如AVL树八叉)。这些树是基于不同种类的旋转来重新平衡树的。

轮换

在此挑战中,我们将仅关注单个右旋,这样的旋转(左旋将是对称的)如下所示:

    5            3
   / \          / \
  3   6   =>   1   5
 / \              / \
1   4            4   6

如果有任何叶子14或者6有左子树或右子树,则旋转只需将它们保留在那里。如果这是较大树的子树,则只需在节点处“将其切除”,5然后将旋转后的树(现在为node 3)“重新连接” 到该节点。

挑战

给定二分搜索树1和关键字,如上所述,该树在该节点上向右旋转。上例中提供的密钥为5

规则和I / O

  • 您可以使用任何类型的键,只要您选择的键与测试用例的键之间存在双射
  • 您可以选择二叉树的任何表示形式,只要没有歧义(例如,[3,[]]除非另有说明,否则是模棱两可的),这对于您选择的语言是很自然的
  • 由于输入将始终是二叉搜索树,因此没有重复的键
  • 您可以假设密钥包含在树中
  • 您可以假定包含密钥的节点有一个左子节点
  • 您可能无法在提供的键下假设正确的子树
  • 您可能认为旋转之前树是不平衡的
  • 您可能认为旋转后树是平衡的
  • 您可以使用任何默认的I / O方法
  • 您提交的内容可能是返回树的功能或打印解决方案的完整程序

测试用例

这些示例表示一棵树,如下所示

  • 如果是一片叶子: []
  • 如果它是带有键的树x并且两个子树都是叶子:[x]
  • 如果它是带有键x和子树的树left right[x,left,right]

第一个示例是“ 旋转 ”部分中提供的示例。如果出于某种原因您需要它们的图形表示,请在此处2

5 [5,[3,[1],[4]],[6]]  ->  [3,[1],[5,[4],[6]]]
5 [5,[3,[1],[4]],[]]  ->  [3,[1],[5,[4],[]]]
5 [5,[3,[],[4]],[6]]  ->  [3,[],[5,[4],[6]]]
5 [5,[3,[1],[]],[]]  ->  [3,[1],[5]]
4 [8,[4,[2,[1],[3]],[6,[5],[7]]],[12,[10,[9],[11]],[14,[13],[15]]]]  ->  [8,[2,[1],[4,[3],[6,[5],[7]]]],[12,[10,[9],[11]],[14,[13],[15]]]]
8 [10,[8,[6,[4,[2,[],[3]],[5]],[7]],[9]],[11]]  ->  [10,[6,[4,[2,[],[3]],[5]],[8,[7],[9]]],[11]]
10 [10,[8,[6,[4,[2,[],[3]],[5]],[7]],[9]],[11]]  ->  [8,[6,[4,[2,[],[3]],[5]],[7]],[10,[9],[11]]]
9 [6,[3,[2],[5]],[9,[8],[12,[11],[15,[14],[]]]]]  ->  [6,[3,[2],[5]],[8,[],[9,[],[12,[11],[15,[14],[]]]]]]
7 [7,[5,[3,[1],[4]],[6]],[8]]  ->  [5,[3,[1],[4]],[7,[6],[8]]]
15 [17,[9,[5,[2,[0],[4]],[8]],[15,[13,[11,[10],[12]],[14]],[16]]],[40,[27,[21,[19,[18],[20]],[24,[22],[25]]],[28]],[44,[42,[41],[]],[51,[47],[59,[55],[61]]]]]]  ->  [17,[9,[5,[2,[0],[4]],[8]],[13,[11,[10],[12]],[15,[14],[16]]]],[40,[27,[21,[19,[18],[20]],[24,[22],[25]]],[28]],[44,[42,[41],[]],[51,[47],[59,[55],[61]]]]]]
21 [17,[9,[5,[2,[0],[4]],[8]],[15,[13,[11,[10],[12]],[14]],[16]]],[40,[27,[21,[19,[18],[20]],[24,[22],[25]]],[28]],[44,[42,[41],[]],[51,[47],[59,[55],[61]]]]]]  ->  [17,[9,[5,[2,[0],[4]],[8]],[15,[13,[11,[10],[12]],[14]],[16]]],[40,[27,[19,[18],[21,[20],[24,[22],[25]]]],[28]],[44,[42,[41],[]],[51,[47],[59,[55],[61]]]]]]

1:对于任何节点,左子树中的所有键都小于该键,而右子树中的所有键都大于该键

2:为防止链接腐烂,我将它们嵌入作为注释

Answers:


8

Haskell93 92 84 83 82字节

data B=B[B]Int|L
k!B[l@(B[x,y]a),r]n|k<n=B[k!l,r]n|k>n=B[l,k!r]n|1>0=B[x,B[y,r]k]a

感谢@ BMO,@ alephalpha和@Laikoni分别为一个字节,@ nimi为八个字节!

在线尝试!


使用data B=B[B]Int将节省更多的字节。
莱科尼'18

@Laikoni我认为只是一个字节,但我会接受它
昂斯

您可以通过先合并两个情况下保存2个字节k<n=B[k!l,r]nk>n=B[l,k!r]n:成一个k/=n=B[k!l,k!r]n,然后加入k!x=x使模式匹配无遗。
Radek '18

5

Vim,25个字节

在缓冲区中输入-空格键和树。该树预期表示如下:

  • 叶: []
  • 具有key的节点k,左子节点<left>和右子节点<right>[ k <left><right>]

关键周围的空间k并不重要,因此该解决方案适用于任意树。

"adw/ <C-r>a⏎3dw%l"apr[%xl%i]

在线尝试!

说明

"adw                           " delete the key and trailing space, keep in register a
    / <C-r>a⏎                  " move cursor to the key surrounded with spaces
             3dw               " remove key and [ (move left node to top)
                %l             " move cursor to the right subtree
                  "ap          " insert key there
                     r[        " insert a [ (appending subtree to key)
                       %       " move to the end of new left subtree
                        x      " remove ] (fix parentheses)
                         l%    " move to the end of new right subtree
                           i]  " insert ] (fix parentheses)

预习

这是Lynn使用脚本生成的第一个测试用例的预览:

                       Vim预览


3

Wolfram语言(Mathematica),30个字节

#2/.a_~b_~c_~#~d_:>b[a,c~#~d]&

在线尝试!

一棵树表示如下:

  • 如果是叶子:($您可以用任何不是键的值替换它)
  • 如果它是带有键x和子树的树left rightx[left,right]

例如,第一个测试用例中的树由表示5[3[1[$,$],4[$,$]],6[$,$]]

说明:

#2                                the second input
  /.                              replace
    a_~b_~c_~#~d_                 #[b[a,c],d], where # is the first input
                 :>               by
                   b[a,c~#~d]     b[a,#[c,d]]
                             &    define a function

3

普通Lisp,146个字节

(defun r(k a)(cond((not a)a)((=(car a)k)`(,(caadr a),(cadadr a)(,(car a),(car(cddadr a)),(caddr a))))(t`(,(car a),(r k(cadr a)),(r k(caddr a))))))

在线尝试验证所有测试用例!

一棵树表示如下:

  • 空树表示为nil(或在Common Lisp中等效为空列表()
  • 非空树表示为三个元素的列表(node left-subtree right-subtree) (因此,叶L表示为(L nil nil))。

2

JavaScript(Node.js),70字节

n=>f=a=>n-a[0]?(a[i=n<a[0]?1:2]=f(a[i]),a):(i=a[1],a[1]=i[2],i[2]=a,i)

在线尝试!链接包括测试用例。所有节点都必须具有左右条目,但是它们可以[]指示该侧没有子树。作为缩写,测试套件用于l(N)指示那N是一片叶子,并l(N,L)指示在输入和输出上N都具有左子树L但没有右子树。


2

Python 2,85个字节

R=lambda T,k:k in T and T[1][:2]+[[T[0],T[1][2],T[2]]]or T[:1]+[R(i,k)for i in T[1:]]

在线尝试!

输入格式:

  • 树: [KEY, LEFT_SUBTREE, RIGHT_SUBTREE]
  • 叶: []

1

果冻,24字节

ñ
Ḣ,1ịṪƊṭ@ṪṭḢð{Ḣ;ç€ɗ¹¡i?

在线尝试!

警告:通常情况下,顶行不应该存在,而底行应包含 ß,而不是ç。但是ß,由于ß的可变Arity。从技术上讲,我可能仍然省略了第一行,但是结果必须是完整的程序,因为否则必须将它作为自己的行合并到任何程序中,除非您没有真幸运。不幸的是,这意味着输出本来就模棱两可,因为当您提交完整的程序时,实际获得的输出计数,而不是程序关闭前的技术结果。因此,为了避免混淆递归和正确的字符串表示,我决定提交一个2行函数,其中最上面一行的工作就是调用最下面一行。结果呢?2个宝贵字节的巨大浪费。在果冻(和丹尼斯,以及其他所有贡献者)的辩护中,

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.