使用《Delphi圣典》中的红黑树实现时,Promote() 函数存在问题。

14
我正在使用Julian Bucknall在他著名的书籍The Tomes Of Delphi中编写的Red-Black树实现。源代码可以从此处下载,我在Delphi 2010中使用原始代码,并对TdBasics.pas进行修改,以便它在现代版本的Delphi中编译(大部分注释掉 - 树代码只需要一些定义)。
这是一位著名作者的众所周知的实现,在一本经常推荐的书中。我认为我应该在坚实的基础上使用它。但是我在使用Delete()Promote()时遇到了崩溃。回退并使用DUnit编写单元测试,这些问题很容易重现。以下是一些示例代码(来自我的DUnit测试片段):
// Tests that require an initialised tree start with one with seven items
const
  NumInitialItems : Integer = 7;

...

// Data is an int, not a pointer
function Compare(aData1, aData2: Pointer): Integer;
begin
  if NativeInt(aData1) < NativeInt(aData2) then Exit(-1);
  if NativeInt(aData1) > NativeInt(aData2) then Exit(1);
  Exit(0);
end;

// Add seven items (0..6) to the tree.  Node.Data is a pointer field, just cast.
procedure TestTRedBlackTree.SetUp;
var
  Loop : Integer;
begin
  FRedBlackTree := TtdRedBlackTree.Create(Compare, nil);
  for Loop := 0 to NumInitialItems - 1 do begin
    FRedBlackTree.Insert(Pointer(Loop));
  end;
end;

...

// Delete() crashes for the first item, no matter if it is 0 or 1 or... 
procedure TestTRedBlackTree.TestDelete;
var
  aItem: Pointer;
  Loop : Integer;
begin
  for Loop := 1 to NumInitialItems - 1 do begin // In case 0 (nil) causes problems, but 1 fails too
    aItem := Pointer(Loop);
    Check(FRedBlackTree.Find(aItem) = aItem, 'Item not found before deleting');
    FRedBlackTree.Delete(aItem);
    Check(FRedBlackTree.Find(aItem) = nil, 'Item found after deleting');
    Check(FRedBlackTree.Count = NumInitialItems - Loop, 'Item still in the tree');
  end;
end;

我对算法不够扎实,不知道如何修复它而不引入进一步的问题(不平衡或不正确的树)。我知道,因为我已经尝试过 :)。

崩溃代码

上面的测试在删除项目时在Promote()中失败,在标记为!!!的行:

function TtdRedBlackTree.rbtPromote(aNode : PtdBinTreeNode)
                                          : PtdBinTreeNode;
var
  Parent : PtdBinTreeNode;
begin
  {make a note of the parent of the node we're promoting}
  Parent := aNode^.btParent;

  {in both cases there are 6 links to be broken and remade: the node's
   link to its child and vice versa, the node's link with its parent
   and vice versa and the parent's link with its parent and vice
   versa; note that the node's child could be nil}

  {promote a left child = right rotation of parent}
  if (Parent^.btChild[ctLeft] = aNode) then begin
    Parent^.btChild[ctLeft] := aNode^.btChild[ctRight];
    if (Parent^.btChild[ctLeft] <> nil) then
      Parent^.btChild[ctLeft]^.btParent := Parent;
    aNode^.btParent := Parent^.btParent;
    if (aNode^.btParent^.btChild[ctLeft] = Parent) then //!!!
      aNode^.btParent^.btChild[ctLeft] := aNode
    else
      aNode^.btParent^.btChild[ctRight] := aNode;
    aNode^.btChild[ctRight] := Parent;
    Parent^.btParent := aNode;
  end
  ...
Parent.btParent(变成aNode.btParent)是nil,因此导致了崩溃。检查树结构时,节点的父节点是根节点,显然它本身也有一个nil的父节点。

一些未能成功修复它的尝试

我尝试简单地测试这个问题,并且只在存在祖父节点时运行if/then/else语句。虽然这似乎很合理,但这是一种天真的解决方法;我不太了解旋转操作,无法确定这是否有效或者是否应该采取其他措施——这样做会导致另一个问题,如代码片段后面所述。(请注意,在上面复制的代码片段下面还有一个左旋转的代码副本,同样会出现相同的错误。)

if aNode.btParent <> nil then begin //!!! Grandparent doesn't exist, because parent is root node
  if (aNode^.btParent^.btChild[ctLeft] = Parent) then
    aNode^.btParent^.btChild[ctLeft] := aNode
  else
    aNode^.btParent^.btChild[ctRight] := aNode;
  aNode^.btChild[ctRight] := Parent;
end;
Parent^.btParent := aNode;
...

使用这段代码,删除测试仍然失败,但有一些奇怪的事情发生:在调用Delete()之后,调用Find()正确地返回nil,表示该项已被移除。然而,在循环的最后一次迭代中,删除项目6会导致TtdBinarySearchTree.bstFindItem崩溃:
Walker := FBinTree.Root;
CmpResult := FCompare(aItem, Walker^.btData);

FBinTree.Rootnil,调用FCompare时会崩溃。

因此,我可以说,我的修改显然只会引起更多问题,而实现算法的代码中存在更基本的问题。不幸的是,即使有书作为参考,我也无法弄清楚哪里出了问题,或者更准确地说,正确的实现应该是什么样子的,这里有什么不同。

我最初认为可能是我的代码错误地使用了树,导致了问题。这仍然是很有可能的!作者、书籍和隐含的代码在Delphi世界中都很有名。但是,通过从作者网站上下载的书籍源代码编写一些非常基本的单元测试来使用该类,崩溃很容易复现。过去十年中肯定还有其他人使用过这段代码,并遇到了相同的问题(除非错误是我的,我的代码和单元测试都错误地使用了树)。我正在寻求以下方面的答案:

  • 识别并修复Promote和类中其他任何错误。请注意,我还为基类TtdBinarySearchTree编写了单元测试,并且所有测试都通过了。(这并不意味着它是完美的 - 我可能没有识别出失败的情况。但这有些帮助。)
  • 查找更新的代码版本。Julian没有发布任何红黑树实现的勘误
  • 如果一切都失败了,请查找Delphi的另一个已知的良好红黑树实现。我正在使用该树来解决问题,而不是为了编写树而使用它。如果必须这样做,我将乐意用另一个实现替换底层实现(在许可条款等方面得到批准)。尽管如此,考虑到该书和代码的来源,问题仍然令人惊讶,解决这些问题将有助于更多的人 - 它是Delphi社区广泛推荐的一本书。

编辑:进一步说明

评论者MBo指出Julian的EZDSL库,其中包含另一个红黑树的实现。该版本的单元测试通过。我目前正在比较这两个源代码,试图找出算法偏差的地方,以找到错误。

一种可能是仅使用EZDSL红黑树,而不是Tomes of Delphi红黑树,但该库存在一些问题,使我不愿使用它:它只适用于32位x86;一些方法仅以汇编提供,而不是Pascal(尽管大多数都有两个版本);树的结构相当不同,例如使用游标到节点而不是指针 - 这是一种完全有效的方法,但是说明代码与ToD书中的“示例”代码在语义上导航有多么不同;在我看来,代码更难理解和使用:它被相当大地优化,变量和方法没有明确命名,有各种神奇的函数,节点结构实际上是一个联合/情况记录,压缩了堆栈,队列,双端队列和列表,双向链接列表,跳过列表,树,二叉树和堆等细节,所有这些细节都在一个结构中,几乎无法在调试器中理解等。这不是我愿意在生产中使用并需要支持的代码,也不容易学习。Tomes of Delphi源代码更易读且更易维护......但也是错误的。你看到了困境:)
我正在尝试比较代码,以查找Julian实践中的EZDSL代码和他的教学代码(Tomes of Delphi)之间的差异。但这个问题仍然没有解决,我仍然会感激有人回答。自从12年前发布以来,我不可能是唯一一个使用Tomes of Delphi中的红黑树的人 :)
编辑:进一步的笔记
我已经自己回答了这个问题(尽管提供了悬赏)。我试图通过检查代码并将其与ToD算法描述进行比较来找到错误,但遇到了麻烦,因此我基于一个带有MIT许可的C实现的良好页面重新实现了有缺陷的方法;详细信息如下。其中一个奖励是,我认为新的实现实际上更容易理解。
2个回答

7
我没有通过查看 Delphi 的源代码,或将其与算法或 Julian 的其他实现(即经过大量优化的 EZDSL 库实现)进行比较,找出问题所在 (因此提出这个问题!)。但是,我已经重新实现了Delete,并额外实现了Insert。这些实现基于文学编程网站上红黑树的 C 代码示例,这是我所找到的最清晰的红黑树示例之一。(当你不完全理解算法时,仅通过分析代码并验证其正确性来查找 bug 实际上是一项相当困难的任务。我可以告诉你,我现在对红黑树有了更好的理解!)该树具有相当完善的文档说明-我认为 Delphi 全书对树有效工作的原因提供了更好的概述,但这份代码是一个更好的可读性实现的例子。
关于此的注释:
  • 评论通常是对某个方法的页面解释的直接引用。
  • 尽管我已将过程性C代码移植到面向对象的结构中,但很容易进行端口转换。有一些小问题,例如Bucknall的树具有一个FHead节点,其子节点是树的根节点,在转换时需要注意此问题。(测试通常测试节点的父节点是否为NULL来测试节点是否为根节点。我将此及其他类似逻辑提取到帮助程序、节点或树方法中。)
  • 读者可能还会发现Eternally Confuzzled page on red-black trees有用。虽然我在编写此实现时没有使用它,但我可能应该这样做,如果此实现出现错误,我将寻求那里的见解。这也是我在调试ToD之一时研究红黑树时找到的第一页,提到了红黑树和2-3-4树之间的关系。
  • 如果不清楚,此代码修改了Tom Delphi示例中的TtdBinaryTreeTtdBinarySearchTreeTtdRedBlackTree,这些示例可以在TDBinTre.pas (ToD页面上的源代码下载)中找到。要使用它,请编辑该文件。这不是一个新实现,也不是完整的实现。具体而言,它保留了ToD代码的结构,例如TtdBinarySearchTree不是TtdBinaryTree的后代,但作为成员拥有一个(即包装它),使用FHead节点而不是nil父节点到Root等。
  • 原始代码在MIT许可下发布。(该站点正在转移到另一种许可证;您检查时可能已更改。对于将来的读者,在撰写本文时,该代码明确处于MIT许可下。)我不确定Tom Delphi代码的许可证;因为它在算法书中,所以可能可以假设您可以使用它-我认为这是参考书中的隐含内容。就我个人而言,只要您遵守原始许可证,就可以使用它:)如果有用,请发表评论,我想知道。
  • Tom Delphi的实现通过使用先辈排序二叉树的插入方法进行插入,然后“提升”节点。逻辑在这两个地方之一。此实现也实现了插入,然后进入许多案例来检查位置并通过显式旋转修改位置。这些旋转在单独的方法(RotateLeftRotateRight)中,我认为这很有用-ToD代码谈到了旋转,但没有明确地将它们提取到单独命名的方法中。Delete类似:进入许多情况。每种情况都在页面上和我的代码中解释。其中一些我命名了,但有些太复杂了,无法放在方法名中,因此只是“case 4”、“case 5”等,并带有解释性注释。
  • 该页面还有验证树结构和红黑属性的代码。我开始作为编写单元测试的一部分而这样

    开始编写代码吧!

    节点修改

    我添加了以下助手方法到节点中,使得代码在阅读时更易理解。例如,原始代码经常通过测试 (盲目转换为 Delphi 和未修改的 ToD 结构) if Node = Node.Parent.btChild[ctLeft] then... 来测试一个节点是否是其父节点的左子节点,而现在您可以测试 if Node.IsLeft then... 等等。记录定义中的方法原型不包含在内以节省空间,但应该很明显:)

    function TtdBinTreeNode.Parent: PtdBinTreeNode;
    begin
      assert(btParent <> nil, 'Parent is nil');
      Result := btParent;
    end;
    
    function TtdBinTreeNode.Grandparent: PtdBinTreeNode;
    begin
      assert(btParent <> nil, 'Parent is nil');
      Result := btParent.btParent;
      assert(Result <> nil, 'Grandparent is nil - child of root node?');
    end;
    
    function TtdBinTreeNode.Sibling: PtdBinTreeNode;
    begin
      assert(btParent <> nil, 'Parent is nil');
      if @Self = btParent.btChild[ctLeft] then
        Exit(btParent.btChild[ctRight])
      else
        Exit(btParent.btChild[ctLeft]);
    end;
    
    function TtdBinTreeNode.Uncle: PtdBinTreeNode;
    begin
      assert(btParent <> nil, 'Parent is nil');
      // Can be nil if grandparent has only one child (children of root have no uncle)
      Result := btParent.Sibling;
    end;
    
    function TtdBinTreeNode.LeftChild: PtdBinTreeNode;
    begin
      Result := btChild[ctLeft];
    end;
    
    function TtdBinTreeNode.RightChild: PtdBinTreeNode;
    begin
      Result := btChild[ctRight];
    end;
    
    function TtdBinTreeNode.IsLeft: Boolean;
    begin
      Result := @Self = Parent.LeftChild;
    end;
    
    function TtdBinTreeNode.IsRight: Boolean;
    begin
      Result := @Self = Parent.RightChild;
    end;
    

    我还添加了额外的方法,比如现有的IsRed(),用于测试它是否为黑色(我的代码扫描更好,如果它说if IsBlack(Node)而不是if not IsRed(Node)),以及获取颜色,包括处理nil节点。请注意,这些需要保持一致性 - 例如,IsRed对于nil节点返回false,因此nil节点是黑色的。(这也与红黑树的属性和到叶子节点路径上的黑节点数量一致。)

    function IsBlack(aNode : PtdBinTreeNode) : boolean;
    begin
      Result := not IsRed(aNode);
    end;
    
    function NodeColor(aNode :PtdBinTreeNode) : TtdRBColor;
    begin
      if aNode = nil then Exit(rbBlack);
      Result := aNode.btColor;
    end;
    

    红黑树约束验证

    如上所述,这些方法验证了树的结构和红黑约束,并直接翻译了原始C代码中的相同方法。如果在类定义中未调试,则Verify声明为内联。如果没有调试,则该方法应为空,希望编译器完全删除它。VerifyInsertDelete方法的开头和结尾被调用,以确保修改前后树的正确性。

    procedure TtdRedBlackTree.Verify;
    begin
    {$ifdef DEBUG}
      VerifyNodesRedOrBlack(FBinTree.Root);
      VerifyRootIsBlack;
      // 3 is implicit
      VerifyRedBlackRelationship(FBinTree.Root);
      VerifyBlackNodeCount(FBinTree.Root);
    {$endif}
    end;
    
    procedure TtdRedBlackTree.VerifyNodesRedOrBlack(const Node : PtdBinTreeNode);
    begin
      // Normally implicitly ok in Delphi, due to type system - can't assign something else
      // However, node uses a union / case to write to the same value, theoretically
      // only for other tree types, so worth checking
      assert((Node.btColor = rbRed) or (Node.btColor = rbBlack));
      if Node = nil then Exit;
      VerifyNodesRedOrBlack(Node.LeftChild);
      VerifyNodesRedOrBlack(Node.RightChild);
    end;
    
    procedure TtdRedBlackTree.VerifyRootIsBlack;
    begin
      assert(IsBlack(FBinTree.Root));
    end;
    
    procedure TtdRedBlackTree.VerifyRedBlackRelationship(const Node : PtdBinTreeNode);
    begin
      // Every red node has two black children; or, the parent of every red node is black.
      if IsRed(Node) then begin
        assert(IsBlack(Node.LeftChild));
        assert(IsBlack(Node.RightChild));
        assert(IsBlack(Node.Parent));
      end;
      if Node = nil then Exit;
      VerifyRedBlackRelationship(Node.LeftChild);
      VerifyRedBlackRelationship(Node.RightChild);
    end;
    
    procedure VerifyBlackNodeCountHelper(const Node : PtdBinTreeNode; BlackCount : NativeInt; var PathBlackCount : NativeInt);
    begin
      if IsBlack(Node) then begin
        Inc(BlackCount);
      end;
    
      if Node = nil then begin
        if PathBlackCount = -1 then begin
          PathBlackCount := BlackCount;
        end else begin
          assert(BlackCount = PathBlackCount);
        end;
        Exit;
      end;
      VerifyBlackNodeCountHelper(Node.LeftChild, BlackCount, PathBlackCount);
      VerifyBlackNodeCountHelper(Node.RightChild, BlackCount, PathBlackCount);
    end;
    
    procedure TtdRedBlackTree.VerifyBlackNodeCount(const Node : PtdBinTreeNode);
    var
      PathBlackCount : NativeInt;
    begin
      // All paths from a node to its leaves contain the same number of black nodes.
      PathBlackCount := -1;
      VerifyBlackNodeCountHelper(Node, 0, PathBlackCount);
    end;
    

    旋转和其他有用的树方法

    提供的方法可以检查节点是否为根节点,将节点设置为根节点,用另一个节点替换一个节点,执行左旋和右旋,以及沿着右侧节点向下跟随树到达叶子节点等。请将这些方法作为红黑树类的受保护成员。

    procedure TtdRedBlackTree.RotateLeft(Node: PtdBinTreeNode);
    var
      R : PtdBinTreeNode;
    begin
      R := Node.RightChild;
      ReplaceNode(Node, R);
      Node.btChild[ctRight] := R.LeftChild;
      if R.LeftChild <> nil then begin
        R.LeftChild.btParent := Node;
      end;
      R.btChild[ctLeft] := Node;
      Node.btParent := R;
    end;
    
    procedure TtdRedBlackTree.RotateRight(Node: PtdBinTreeNode);
    var
      L : PtdBinTreeNode;
    begin
      L := Node.LeftChild;
      ReplaceNode(Node, L);
      Node.btChild[ctLeft] := L.RightChild;
      if L.RightChild <> nil then begin
        L.RightChild.btParent := Node;
      end;
      L.btChild[ctRight] := Node;
      Node.btParent := L;
    end;
    
    procedure TtdRedBlackTree.ReplaceNode(OldNode, NewNode: PtdBinTreeNode);
    begin
      if IsRoot(OldNode) then begin
        SetRoot(NewNode);
      end else begin
        if OldNode.IsLeft then begin // // Is the left child of its parent
          OldNode.Parent.btChild[ctLeft] := NewNode;
        end else begin
          OldNode.Parent.btChild[ctRight] := NewNode;
        end;
      end;
      if NewNode <> nil then begin
        newNode.btParent := OldNode.Parent;
      end;
    end;
    
    function TtdRedBlackTree.IsRoot(const Node: PtdBinTreeNode): Boolean;
    begin
      Result := Node = FBinTree.Root;
    end;
    
    procedure TtdRedBlackTree.SetRoot(Node: PtdBinTreeNode);
    begin
      Node.btColor := rbBlack; // Root is always black
      FBinTree.SetRoot(Node);
      Node.btParent.btColor := rbBlack; // FHead is black
    end;
    
    function TtdRedBlackTree.MaximumNode(Node: PtdBinTreeNode): PtdBinTreeNode;
    begin
      assert(Node <> nil);
      while Node.RightChild <> nil do begin
        Node := Node.RightChild;
      end;
      Result := Node;
    end;
    

    插入和删除

    红黑树是一个内部树FBinTree的封装。这段代码直接修改了树,使其过于紧密连接。二叉搜索树和红黑树类都保留了节点数计数器FCount。为了使代码更简洁,我删除了红黑树祖先TtdBinarySearchTreeFCount并将Count重定向到FBinTree.Count,即询问实际的内部树,这是二叉搜索树和红黑树类使用的东西 - 它毕竟拥有节点。我还添加了通知方法NodeInsertedNodeRemoved来增加和减少计数。代码未包含(微不足道)。

    我还提取了一些用于分配节点和释放节点的方法 - 不是为了插入或从树中删除,也不是为了处理节点的连接或存在;而是为了照顾节点本身的创建和销毁。请注意,节点创建需要将节点的颜色设置为红色 - 颜色变化在此之后进行处理。这还确保在释放节点时,有机会释放与其关联的数据。

    function TtdBinaryTree.NewNode(const Item : Pointer): PtdBinTreeNode;
    begin
      {allocate a new node }
      Result := BTNodeManager.AllocNode;
      Result^.btParent := nil;
      Result^.btChild[ctLeft] := nil;
      Result^.btChild[ctRight] := nil;
      Result^.btData := Item;
      Result.btColor := rbRed; // Red initially
    end;
    
    procedure TtdBinaryTree.DisposeNode(Node: PtdBinTreeNode);
    begin
      // Free whatever Data was pointing to, if necessary
      if Assigned(FDispose) then FDispose(Node.btData);
      // Free the node
      BTNodeManager.FreeNode(Node);
      // Decrement the node count
      NodeRemoved;
    end;
    

    使用这些额外的方法,可以使用以下代码进行插入和删除。代码已经注释,但我建议您阅读原始页面以及Tom的Delphi书籍,以了解旋转的说明,代码测试的各种情况。

    插入

    procedure TtdRedBlackTree.Insert(aItem : pointer);
    var
      NewNode, Node : PtdBinTreeNode;
      Comparison : NativeInt;
    begin
      Verify;
      newNode := FBinTree.NewNode(aItem);
      assert(IsRed(NewNode)); // new node is red
      if IsRoot(nil) then begin
        SetRoot(NewNode);
        NodeInserted;
      end else begin
        Node := FBinTree.Root;
        while True do begin
          Comparison := FCompare(aItem, Node.btData);
          case Comparison of
            0: begin
              // Equal: tree doesn't support duplicate values
              assert(false, 'Should not insert a duplicate item');
              FBinTree.DisposeNode(NewNode);
              Exit;
            end;
            -1: begin
              if Node.LeftChild = nil then begin
                Node.btChild[ctLeft] := NewNode;
                Break;
              end else begin
                Node := Node.LeftChild;
              end;
            end;
            else begin
              assert(Comparison = 1, 'Only -1, 0 and 1 are valid comparison values');
              if Node.RightChild = nil then begin
                Node.btChild[ctRight] := NewNode;
                Break;
              end else begin
                Node := Node.RightChild;
              end;
            end;
          end;
        end;
        NewNode.btParent := Node; // Because assigned to left or right child above
        NodeInserted; // Increment count
      end;
      InsertCase1(NewNode);
      Verify;
    end;
    
    // Node is now the root of the tree.  Node must be black; because it's the only
    // node, there is only one path, so the number of black nodes is ok
    procedure TtdRedBlackTree.InsertCase1(Node: PtdBinTreeNode);
    begin
      if not IsRoot(Node) then begin
        InsertCase2(Node);
      end else begin
        // Node is root (the less likely case)
        Node.btColor := rbBlack;
      end;
    end;
    
    // New node has a black parent: all properties ok
    procedure TtdRedBlackTree.InsertCase2(Node: PtdBinTreeNode);
    begin
      // If it is black, then everything ok, do nothing
      if not IsBlack(Node.Parent) then InsertCase3(Node);
    end;
    
    // More complex: uncle is red. Recolor parent and uncle black and grandparent red
    // The grandparent change may break the red-black properties, so start again
    // from case 1.
    procedure TtdRedBlackTree.InsertCase3(Node: PtdBinTreeNode);
    begin
      if IsRed(Node.Uncle) then begin
        Node.Parent.btColor := rbBlack;
        Node.Uncle.btColor := rbBlack;
        Node.Grandparent.btColor := rbRed;
        InsertCase1(Node.Grandparent);
      end else begin
        InsertCase4(Node);
      end;
    end;
    
    // "In this case, we deal with two cases that are mirror images of one another:
    // - The new node is the right child of its parent and the parent is the left child
    // of the grandparent. In this case we rotate left about the parent.
    // - The new node is the left child of its parent and the parent is the right child
    // of the grandparent. In this case we rotate right about the parent.
    // Neither of these fixes the properties, but they put the tree in the correct form
    // to apply case 5."
    procedure TtdRedBlackTree.InsertCase4(Node: PtdBinTreeNode);
    begin
      if (Node.IsRight) and (Node.Parent = Node.Grandparent.LeftChild) then begin
        RotateLeft(Node.Parent);
        Node := Node.LeftChild;
      end else if (Node.IsLeft) and (Node.Parent = Node.Grandparent.RightChild) then begin
        RotateRight(Node.Parent);
        Node := Node.RightChild;
      end;
      InsertCase5(Node);
    end;
    
    // " In this final case, we deal with two cases that are mirror images of one another:
    // - The new node is the left child of its parent and the parent is the left child
    // of the grandparent. In this case we rotate right about the grandparent.
    // - The new node is the right child of its parent and the parent is the right child
    // of the grandparent. In this case we rotate left about the grandparent.
    // Now the properties are satisfied and all cases have been covered."
    procedure TtdRedBlackTree.InsertCase5(Node: PtdBinTreeNode);
    begin
      Node.Parent.btColor := rbBlack;
      Node.Grandparent.btColor := rbRed;
      if (Node.IsLeft) and (Node.Parent = Node.Grandparent.LeftChild) then begin
        RotateRight(Node.Grandparent);
      end else begin
        assert((Node.IsRight) and (Node.Parent = Node.Grandparent.RightChild));
        RotateLeft(Node.Grandparent);
      end;
    end;
    

    Deletion

    procedure TtdRedBlackTree.Delete(aItem : pointer);
    var
      Node,
      Predecessor,
      Child : PtdBinTreeNode;
    begin
      Node := bstFindNodeToDelete(aItem);
      if Node = nil then begin
        assert(false, 'Node not found');
        Exit;
      end;
      if (Node.LeftChild <> nil) and (Node.RightChild <> nil) then begin
        Predecessor := MaximumNode(Node.LeftChild);
        Node.btData := aItem;
        Node := Predecessor;
      end;
    
      assert((Node.LeftChild = nil) or (Node.RightChild = nil));
      if Node.LeftChild = nil then
        Child := Node.RightChild
      else
        Child := Node.LeftChild;
    
      if IsBlack(Node) then begin
        Node.btColor := NodeColor(Child);
        DeleteCase1(Node);
      end;
      ReplaceNode(Node, Child);
      if IsRoot(Node) and (Child <> nil) then begin
        Child.btColor := rbBlack;
      end;
    
      FBinTree.DisposeNode(Node);
    
      Verify;
    end;
    
    // If Node is the root node, the deletion removes one black node from every path
    // No properties violated, return
    procedure TtdRedBlackTree.DeleteCase1(Node: PtdBinTreeNode);
    begin
      if IsRoot(Node) then Exit;
      DeleteCase2(Node);
    end;
    
    // Node has a red sibling; swap colors, and rotate so the sibling is the parent
    // of its former parent.  Continue to one of the next cases
    procedure TtdRedBlackTree.DeleteCase2(Node: PtdBinTreeNode);
    begin
      if IsRed(Node.Sibling) then begin
        Node.Parent.btColor := rbRed;
        Node.Sibling.btColor := rbBlack;
        if Node.IsLeft then begin
          RotateLeft(Node.Parent);
        end else begin
          RotateRight(Node.Parent);
        end;
      end;
      DeleteCase3(Node);
    end;
    
    // Node's parent, sibling and sibling's children are black; paint the sibling red.
    // All paths through Node now have one less black node, so recursively run case 1
    procedure TtdRedBlackTree.DeleteCase3(Node: PtdBinTreeNode);
    begin
      if IsBlack(Node.Parent) and
         IsBlack(Node.Sibling) and
         IsBlack(Node.Sibling.LeftChild) and
         IsBlack(Node.Sibling.RightChild) then
      begin
        Node.Sibling.btColor := rbRed;
        DeleteCase1(Node.Parent);
      end else begin
        DeleteCase4(Node);
      end;
    end;
    
    // Node's sibling and sibling's children are black, but node's parent is red.
    // Swap colors of sibling and parent Node; restores the tree properties
    procedure TtdRedBlackTree.DeleteCase4(Node: PtdBinTreeNode);
    begin
      if IsRed(Node.Parent) and
         IsBlack(Node.Sibling) and
         IsBlack(Node.Sibling.LeftChild) and
         IsBlack(Node.Sibling.RightChild) then
      begin
        Node.Sibling.btColor := rbRed;
        Node.Parent.btColor := rbBlack;
      end else begin
        DeleteCase5(Node);
      end;
    end;
    
    // Mirror image cases: Node's sibling is black, sibling's left child is red,
    // sibling's right child is black, and Node is the left child.  Swap the colors
    // of sibling and its left sibling and rotate right at S
    // And vice versa: Node's sibling is black, sibling's right child is red, sibling's
    // left child is black, and Node is the right child of its parent.  Swap the colors
    // of sibling and its right sibling and rotate left at the sibling.
    procedure TtdRedBlackTree.DeleteCase5(Node: PtdBinTreeNode);
    begin
      if Node.IsLeft and
         IsBlack(Node.Sibling) and
         IsRed(Node.Sibling.LeftChild) and
         IsBlack(Node.Sibling.RightChild) then
      begin
        Node.Sibling.btColor := rbRed;
        Node.Sibling.LeftChild.btColor := rbBlack;
        RotateRight(Node.Sibling);
      end else if Node.IsRight and
        IsBlack(Node.Sibling) and
        IsRed(Node.Sibling.RightChild) and
        IsBlack(Node.Sibling.LeftChild) then
      begin
        Node.Sibling.btColor := rbRed;
        Node.Sibling.RightChild.btColor := rbBlack;
        RotateLeft(Node.Sibling);
      end;
      DeleteCase6(Node);
    end;
    
    // Mirror image cases:
    // - "N's sibling S is black, S's right child is red, and N is the left child of its
    // parent. We exchange the colors of N's parent and sibling, make S's right child
    // black, then rotate left at N's parent.
    // - N's sibling S is black, S's left child is red, and N is the right child of its
    // parent. We exchange the colors of N's parent and sibling, make S's left child
    // black, then rotate right at N's parent.
    // This accomplishes three things at once:
    // - We add a black node to all paths through N, either by adding a black S to those
    // paths or by recoloring N's parent black.
    // - We remove a black node from all paths through S's red child, either by removing
    // P from those paths or by recoloring S.
    // - We recolor S's red child black, adding a black node back to all paths through
    // S's red child.
    // S's left child has become a child of N's parent during the rotation and so is
    // unaffected."
    procedure TtdRedBlackTree.DeleteCase6(Node: PtdBinTreeNode);
    begin
      Node.Sibling.btColor := NodeColor(Node.Parent);
      Node.Parent.btColor := rbBlack;
      if Node.IsLeft then begin
        assert(IsRed(Node.Sibling.RightChild));
        Node.Sibling.RightChild.btColor := rbBlack;
        RotateLeft(Node.Parent);
      end else begin
        assert(IsRed(Node.Sibling.LeftChild));
        Node.Sibling.LeftChild.btColor := rbBlack;
        RotateRight(Node.Parent);
      end;
    end;
    

    最后说明

    • 希望这篇翻译对您有所帮助! 如果您发现它有用,请留下评论告诉我您如何使用它,我很想知道。
    • 本代码没有任何担保或保证。它通过了我的单元测试,但测试可能不够全面-我只能说这段代码可以成功地执行Tomes of Delphi代码无法执行的任务。其他方面是否失败还未可知。请自行决定是否使用,我建议您为其编写测试。如果您发现错误,请在此处发表评论!
    • 祝您使用愉快 :)

0
Bucknall写道,他的二叉树实现使用虚拟头节点作为根节点的父节点(以避免特殊情况)。这个头节点在构造函数中创建:
  constructor TtdBinaryTree.Create
   ...
 {allocate a head node, eventually the root node of the tree will be
   its left child}
  FHead := BTNodeManager.AllocNodeClear;

并在第一个节点插入期间使用:

function TtdBinaryTree.InsertAt
  ...
  {if the parent node is nil, assume this is inserting the root}
  if (aParentNode = nil) then begin
    aParentNode := FHead;
    aChildType := ctLeft;
  end;

所以你的情况 "节点的父节点是根节点,而根节点本身显然没有父节点" 看起来非常奇怪,除非你已经重写了关键方法


我可能犯了一个错误,可能是指头节点 - 我需要检查一下。我并没有重写任何方法;事实上,我故意回到了书中提供的完全相同的代码进行测试,以确保问题在于树代码本身,而不是我所做的任何事情。 - David
我无法在D7中编译链接的文件包,但修改过的源代码(http://www.boyet.com/FixedArticles/EZDSL.html)已经成功编译并测试可用(其中包括对D7和D2006的补充)。 - MBo
有趣 - 这是相同的数据结构,但是使用了不同的库(从代码来看非常不同 - 其中很多都是汇编优化的)。而且这个库也是由同一位作者开发的。我会在一两个小时后回到工作中进行调查。感谢提供链接! - David
我已编辑我的问题,以反映EZDSL库。你是对的,它可以工作,但我不想使用这个代码——它不清楚,我认为难以维护。我仍然想弄清楚Tomos of Delphi版本出了什么问题。毕竟,那是一本教学书,应该有明确和正确的示例代码——找到那里的错误将对许多人有价值。 - David

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接