Java 从一棵自上而下的2-3-4左倾红黑树上删除需要什么额外的旋转?

Java 从一棵自上而下的2-3-4左倾红黑树上删除需要什么额外的旋转?,java,algorithm,data-structures,red-black-tree,2-3-4-tree,Java,Algorithm,Data Structures,Red Black Tree,2 3 4 Tree,我已经实现了一个LLRB包,它应该能够在两种模式中的任意一种模式下运行,自下而上的2-3或自上而下的2-3-4(-改进的代码,尽管只处理2-3树,多亏了指针的RS) Sedgewick非常清楚地描述了2-3模式的树操作,尽管他花了很多时间谈论2-3-4模式。他还展示了如何在插入过程中简单地改变颜色翻转的顺序来改变树的行为(或者向下拆分为2-3-4,或者向上拆分为2-3): 第一列是工作台名称,第二列是操作数,第三列是结果。以i5M 2.27为基准 我已经查看了2-3树和2-3-4树的分支长度,其

我已经实现了一个LLRB包,它应该能够在两种模式中的任意一种模式下运行,自下而上的2-3或自上而下的2-3-4(-改进的代码,尽管只处理2-3树,多亏了指针的RS)

Sedgewick非常清楚地描述了2-3模式的树操作,尽管他花了很多时间谈论2-3-4模式。他还展示了如何在插入过程中简单地改变颜色翻转的顺序来改变树的行为(或者向下拆分为2-3-4,或者向上拆分为2-3):

第一列是工作台名称,第二列是操作数,第三列是结果。以i5M 2.27为基准

我已经查看了2-3树和2-3-4树的分支长度,其中几乎没有解释检索差异(从根到节点的平均距离和1000棵树的S.D.,每棵树有10000个随机插入):


更新并验证

测试这一点的关键重要性在于,实现不支持删除不存在或以前删除的节点!我花了太长时间试图弄明白为什么我的工作解决方案被“破坏”。这可以通过对键进行初步搜索并返回false(如果它根本不在树中)来解决,该解决方案已在底部的链接代码中使用

Sedgewick似乎没有为公开的2-3-4删除编写删除。他的结果专门针对2-3棵树(他只是粗略地提到了2-3-4棵树,因为它们的平均路径长度(以及由此产生的搜索成本)以及其他红黑树的平均路径长度与2-3的情况无法区分)。其他人似乎也没有一个很容易找到,因此我在调试问题后发现了以下内容:

首先,使用Sedgewick的代码并修复过时的位。在幻灯片(第31页)中,您可以看到他的代码仍然使用4个节点的旧表示法,在这4个节点中,它是通过在一行中使用两个左红色来完成的,而不是平衡。然后,编写2-3-4删除例程的第一位是修复此问题,以便我们可以进行健全性检查,这将有助于我们稍后验证修复:

private boolean is234(Node x)
{         
   if (x == null)
      return true;
   // Note the TD234 check is here because we also want this method to verify 2-3 trees
   if (isRed(x.right))
      return species == TD234 && isRed(x.left);

   if (!isRed(x.right))
      return true;

   return is234(x.left) && is234(x.right);
} 
一旦我们有了这个,我们知道一些事情。第一,从论文中我们看到,当使用2-3-4树时,4个节点不应该在上升过程中被破坏。第二,在搜索路径上有一个右4节点的特殊情况。还有第三种特殊情况没有提到,那就是当你回到树上时,你可能会在
h.right.left
变为红色的地方结束,只需向左旋转一圈就无效了。这是本文第4页所述的插入案例的镜像

您需要的4节点的旋转修复如下所示:

    private Node moveRedLeft(Node h)
    {  // Assuming that h is red and both h.left and h.left.left
       // are black, make h.left or one of its children red.
       colorFlip(h);
       if (isRed(h.right.left))
       { 
          h.right = rotateRight(h.right);

          h = rotateLeft(h);
          colorFlip(h);

          if (isRed(h.right.right) )
             h.right = rotateLeft(h.right);
       }
      return h;
    }
这将删除2-3-4上的拆分,并为第三种特殊情况添加修复

private Node fixUp(Node h)
{
   if (isRed(h.right))
   {      
      if (species == TD234 && isRed(h.right.left))
         h.right = rotateRight(h.right);
      h = rotateLeft(h);
   }

   if (isRed(h.left) && isRed(h.left.left))
      h = rotateRight(h);

   if (species == BU23 && isRed(h.left) && isRed(h.right))
      colorFlip(h);

   return setN(h);
}
最后,我们需要对其进行测试并确保其工作正常。它们不必是最高效的,但正如我在调试过程中发现的,它们必须实际使用预期的树行为(即不插入/删除重复数据)!我用一个测试助手方法完成了这项工作。当我调试时,我会打断并检查树是否存在明显的不平衡。我已经用100000个节点尝试了这种方法,它的性能完美无瑕:

   public static boolean Test()
   {
      return Test(System.nanoTime());
   }
   public static boolean Test(long seed)
   {
      StdOut.println("Seeding test with: " + seed);
      Random r = new Random(seed);
      RedBlackBST<Integer, Integer> llrb = new RedBlackBST<Integer,Integer>(TD234);
      ArrayList<Integer> treeValues = new ArrayList<Integer>();
      for (int i = 0; i < 1000; i++)
      {
         int val = r.nextInt();
         if (!treeValues.contains(val))
         {
            treeValues.add(val);
            llrb.put(val, val);
         }
         else
            i--;
      }
      for (int i = 0; i < treeValues.size(); i++)
      {
         llrb.delete(treeValues.get(i));
         if (!llrb.check())
         {
            return false;
         }
//         StdDraw.clear(Color.GRAY);
//         llrb.draw(.95, .0025, .008);
      }
      return true;
   }
公共静态布尔测试()
{
返回测试(System.nanoTime());
}
公共静态布尔测试(长种子)
{
StdOut.println(“种子试验:”+种子);
随机r=新随机(种子);
redblackst llrb=新的redblackst(TD234);
ArrayList treeValues=新的ArrayList();
对于(int i=0;i<1000;i++)
{
int val=r.nextInt();
如果(!treeValues.contains(val))
{
treeValues.add(val);
llrb.put(val,val);
}
其他的
我--;
}
对于(int i=0;i

可以找到完整的来源。

“这个问题表明了研究的努力。”-嗯。。。检查……我在各种RBT实现中发现了多个可以在线找到的bug(例如Thomas Niemann的“排序和搜索算法”,这是一个介绍文本+C代码)。遗憾的是,我不记得所有的细节,除了Cormen/Leiserson/Rivest/Stein的著名著作《算法简介》(Introduction To Algorithms,Introduction To Algorithms)中提供的错误参考伪代码(也用于Niemann的代码)。有关详细信息,请参阅。我同意,有很多糟糕/草率的代码描述了这一点。似乎我的旧RBT实现在删除路径中确实有一个额外的块。我甚至在那里有一个类似这样的评论:“修复被删除节点的子节点为NIL的情况(这在其他几个RB树文档和源代码中缺失,该错误的净影响是树中所有终端节点的红黑高度都不相同,因为对数高度特性被破坏了。”。除此之外,我还需要更新我的RBT知识并学习代码。Sedgewick的deleteMin实现是错误的。请尝试插入3个节点,以便得到高度为1的树。完成此操作后,请尝试调用deleteMin两次,之后会得到一个空树,但第三个元素如何?恐怕无法修复实际上,它打破了2-3模式(我怀疑你已经反转了模式测试,但改变了模式并不能解决问题)。编辑,是的,我已经翻转了模式。你看到什么情况不起作用,因为图像是我在递归过程中走下并备份的(尽管是通过
deleteMax()
,因为这就是
delete(15)
相当于。现在设置JDK,这样我就可以运行快速测试了
private boolean is234(Node x)
{         
   if (x == null)
      return true;
   // Note the TD234 check is here because we also want this method to verify 2-3 trees
   if (isRed(x.right))
      return species == TD234 && isRed(x.left);

   if (!isRed(x.right))
      return true;

   return is234(x.left) && is234(x.right);
} 
    private Node moveRedLeft(Node h)
    {  // Assuming that h is red and both h.left and h.left.left
       // are black, make h.left or one of its children red.
       colorFlip(h);
       if (isRed(h.right.left))
       { 
          h.right = rotateRight(h.right);

          h = rotateLeft(h);
          colorFlip(h);

          if (isRed(h.right.right) )
             h.right = rotateLeft(h.right);
       }
      return h;
    }
private Node fixUp(Node h)
{
   if (isRed(h.right))
   {      
      if (species == TD234 && isRed(h.right.left))
         h.right = rotateRight(h.right);
      h = rotateLeft(h);
   }

   if (isRed(h.left) && isRed(h.left.left))
      h = rotateRight(h);

   if (species == BU23 && isRed(h.left) && isRed(h.right))
      colorFlip(h);

   return setN(h);
}
   public static boolean Test()
   {
      return Test(System.nanoTime());
   }
   public static boolean Test(long seed)
   {
      StdOut.println("Seeding test with: " + seed);
      Random r = new Random(seed);
      RedBlackBST<Integer, Integer> llrb = new RedBlackBST<Integer,Integer>(TD234);
      ArrayList<Integer> treeValues = new ArrayList<Integer>();
      for (int i = 0; i < 1000; i++)
      {
         int val = r.nextInt();
         if (!treeValues.contains(val))
         {
            treeValues.add(val);
            llrb.put(val, val);
         }
         else
            i--;
      }
      for (int i = 0; i < treeValues.size(); i++)
      {
         llrb.delete(treeValues.get(i));
         if (!llrb.check())
         {
            return false;
         }
//         StdDraw.clear(Color.GRAY);
//         llrb.draw(.95, .0025, .008);
      }
      return true;
   }