红黑树(Red-BlackBalancedSearchTree)

1.什么是红黑树

  红黑树本质上是二叉搜索树的改良版,因此,对二叉搜索树不了解的,建议先去看一下二叉搜索树

  二叉搜索树有个严重的缺陷:树本身并不平衡,很容易造成部分分支过长,而部分分支过短的情况,从而影响到了搜索速度。

  二叉搜索树的一个极端例子:

  

  如果把一个递增的数组按顺序放进二叉搜索树,就会出现上图的情况,要搜索某个值就得查N次。

  红黑树就可以解决这个问题,且能保证在大部分情况下,树都是平衡的。

 (红黑树图中的空节点只是为了表示另一个节点是左节点还是右节点。)

  要深入理解红黑树的含义,就需要先了解2-3搜索树。

2.2-3搜索树

  2-3搜索树也是二叉搜索树改良版,也能保证树的平衡。这里只介绍2-3搜索树,不用代码实现它。

  

  如上图,2-3搜索树有两种节点:有两个分支的节点和有三个分支的节点。

  有两个分支的节点中,左子节点比父节点小,右子节点比父节点大。

  有三个分支的节点中,左子节点比父节点里的所有数小,右子节点比父节点所有数大,中子节点介于父节点的两个数之间,如下图:

  c<a<d<b<e

插入数值

  从例子介绍思路:

  现有2-3搜索树如下图。

  

  如果要插入值13,首先从根节点开始比较,13<15,故13走向左节点。

  13>10,但由于左节点没有右子节点,因此10和13成为有三个分支的节点。如下图。

  

  如果再插入值14,首先从根节点开始比较,14<15,故14走向左节点。

  14>13,但由于此节点没有右子节点,因此10,13,14成为新节点,但这个含有3个值的节点只是临时的,会经历下一段变化。

  

  含有3个值的节点A中,处于中间值的13会移到此节点的父节点中。即根节点变成含有13,15的新节点。节点A分裂成两个只含1个值的节点。如下图:

  

  如果再插入值25,首先从根节点开始比较,25>15,故25走向右节点。

  25>20,但由于右节点没有右子节点,因此20和25成为有三个分支的节点。如下图。

  

  如果再插入值30,首先从根节点开始比较,30>15,故30走向右节点。

  30>25,但由于右节点没有右子节点,因此20,25,30成为新节点;

  然后25移到父节点,20,30分别成为新节点,如下图:

  

  然后含有13,15,25的节点分裂,15成为新的根节点,13,25是15的子节点,如下图:

  

  如此类推。

  2-3搜索树介绍到这,下面将介绍红黑树。

3.红黑树

  红黑树与2-3搜索树本质上很接近。红黑树有两种节点:红色节点与黑色节点。

  红色节点表示此节点与它的父节点的联系是红色的。  

  黑色节点表示此节点与它的父节点的联系是黑色的。

  如果把红色相连的两个节点看成一个节点,则变成了2-3搜索树中的含有三个分支的节点,如下图:

                                                   

       (红黑树图中的空节点只是为了表示另一个节点是左节点还是右节点。)

  如果我们对根节点为19的2-3搜索树插入值34,则会变成下图:(从左图逐步演变成右图)

 

  如果我们对根节点为19的红黑树插入值34,按照红黑树的游戏规则,会变成下图:(从左图逐步演变成右图)

  

  上图演变过程按顺序经历了左旋、右旋、反转颜色、左旋三种变化。(红黑树只有这三种变化。)

  由上图对比可知,2-3搜索树是把中间值34推到了30和35的上一层;红黑树是把中间值34推到了35的上一层。其实,红黑树就是2-3搜索树的改进版,因为2-3搜索树有两种节点,所以实现起来相当复杂,红黑树就相对简单多了。

   我们只需了解了这三种变化是怎么操作的和2-3搜索树的变化原理,就能理解红黑树的原理。

  红黑树每次插入的新节点都是红色的!而这个新节点比二叉搜索树的节点只多了一个名为Color的bool变量。红色Red为true,黑色Black为false。

红黑树节点代码:

UCLASS()
class ALGORITHM_API ARedBlackNode : public AActor
{
    GENERATED_BODY()
    
public:    
    // Sets default values for this actor's properties
    ARedBlackNode();
    // Called every frame
    virtual void Tick(float DeltaTime) override;
    //设值
    FORCEINLINE void SetValue(int Newkey, FString NewValue)
    {
        Key = Newkey;
        Value = NewValue;
    }

    FORCEINLINE ARedBlackNode* Get() { return this; }
    //获取或修改私有变量
    FORCEINLINE int GetKey() { return Key; }
    FORCEINLINE void SetKey(int NewKey) { Key = NewKey; }
    FORCEINLINE int GetCount() { return Count; }
    FORCEINLINE void SetCount(int NewCount) { Count = NewCount; }
    FORCEINLINE FString GetValue() { return Value; }
    FORCEINLINE void SetValue(FString NewValue) { Value = NewValue; }
    FORCEINLINE bool GetColor() { return Color; }
    FORCEINLINE void SetColor(bool IsRed) { Color = IsRed; }
    FORCEINLINE ARedBlackNode* GetParent() { return Parent; }
    FORCEINLINE void SetParent(ARedBlackNode* X) { Parent = X; }
    FORCEINLINE ARedBlackNode* GetNode(bool Left)
    {
        if (Left) return LeftNode;
        return RightNode;
    }
    FORCEINLINE void SetNode(bool Left, ARedBlackNode* NewNode)
    {
        if (Left)  LeftNode = NewNode;
        else
        {
            RightNode = NewNode;
        }
    }
    

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

private:
    int Key;
    FString Value;
    //左右节点
    ARedBlackNode* LeftNode;
    ARedBlackNode* RightNode;
    //父节点,这个节点是为了记录每个节点的位置(用于测试程序是否正确建立红黑树),与红黑树的实现无关。
    ARedBlackNode* Parent;
    //计算此节点下面共用多少个节点(包括自己)
    int Count;
    //与父节点之间的联系,如果为True,则是红色的;如果为False,则是黑色的
    bool Color;
    
};

  下面,我们将介绍这三种变化:

左旋:

   游戏规则一:在红黑树中,所有红色的联系都是向左的。

   所以当插入的新值与父节点形成的红色联系向右时,我们需要通过左旋来把它纠正。

   也是从例子入手:

  

  这里发现了一个向右的红色联系,需要左旋。我们关注的对象是25和34。

  左旋后,25和34还是红色联系,但34在上面,25成为了34的左节点。

  34本来的左节点30是介于25和34之间的。由于25成为了34的左节点,34的右节点不变,34节点的左右子节点满负荷,节点30游离了出来。

  25的左节点不变,25的右节点空出来了。节点30补到了25的右节点处。变成下图:

  

实现代码:

  

ARedBlackNode* ARedBlackBST::RotateLeft(ARedBlackNode* h)
{
    //X节点是h的右节点。
    //左旋的结果是:
    //1.X成为了h的父节点,h是X的左节点。
    //2.X原来的左节点变成了h的右节点,其它节点不变,h和X之间的联系还是红色。
    ARedBlackNode* X = h->GetNode(false);
    h->SetNode(false, X->GetNode(true));
    X->SetNode(true, h);
    X->SetColor(h->GetColor());
    h->SetColor(Red);
    //左旋后,两节点的Count更新
    h->SetCount(1 + Size(h->GetNode(false)) + Size(h->GetNode(true)));
    X->SetCount(1 + Size(X->GetNode(false)) + Size(X->GetNode(true)));
    UKismetSystemLibrary::PrintString(this, "RotateLeft");
    return X;
}

右旋:

  按道理来讲,由于在红黑树中,所有红色的联系都是向左的。右旋看起来是没必要的。

  但是,游戏规则二:在红黑树中,两个红色的联系不能连续出现。

  即这种情况:

  

  这个规则从2-3搜索树中理解是:30,34,35形成了一个含有3个值的临时节点,需要把中间值挤到上一层去。

  为了不造成误解,我们用a,b放入上述例子中(a>35,34<b<35)。这里假设存在整数b满足这个要求。  

  

  右旋时,我们关注的是第一个红色联系,即这里的34和35。

  如左旋类似的:

  右旋后,35和34还是红色联系,但34在上面,35成为了34的右节点。

  34本来的右节点b是介于35和34之间的。由于35成为了34的右节点,34的左节点不变,34节点的左右子节点满负荷,节点b游离了出来。

  35的右节点不变,35的左节点空出来了。节点b补到了35的左节点处。变成下图:

   

  

实现代码:

ARedBlackNode* ARedBlackBST::RotateRight(ARedBlackNode* h)
{
    //X节点是h的左节点。
    //左旋的结果是:
    //1.X成为了h的父节点,h是X的右节点。
    //2.X原来的右节点变成了h的左节点,其它节点不变,h和X之间的联系还是红色。
    ARedBlackNode* X = h->GetNode(true);
    h->SetNode(true, X->GetNode(false));
    X->SetNode(false, h);
    X->SetColor(h->GetColor());
    h->SetColor(Red);
    //右旋后,两节点的Count更新
    h->SetCount(1 + Size(h->GetNode(false)) + Size(h->GetNode(true)));
    X->SetCount(1 + Size(X->GetNode(false)) + Size(X->GetNode(true)));
    UKismetSystemLibrary::PrintString(this, "RotateRight");
    return X;
}

 反转颜色:

  游戏规则三:一个节点与左节点和右节点的联系不能同时为红色。

  即这种情况:

  

  反转颜色很简单,从2-3搜索树中理解是:30,34,35形成了一个含有3个值的临时节点,需要把中间值挤到上一层去。

  我们只需要把这两个红色联系改为黑色,并把34和34的父节点的联系改为红色即可。(注意:由于根节点没父节点,所以根节点永远是黑色。)

  如图:

  

实现代码:

void ARedBlackBST::FlipColors(ARedBlackNode* h)
{
    //反转颜色:如果一个节点的左右联系都是红色,则将它们变为黑色,此节点与父节点的联系变为红色
    h->SetColor(Red);
    h->GetNode(true)->SetColor(Black);
    h->GetNode(false)->SetColor(Black);
    h->SetValue("");
}

4.红黑树除了删除功能之外的各种功能实现

   了解了红黑树的3种变化后,实现构建红黑树的难度就不高了。我们只需在插入新值时,判断一下节点是否需要左旋、右旋或者反转颜色,如果需要则进行相关操作即可。

插入新值的实现代码:  

void ARedBlackBST::Put(int Newkey)
{
    RootNode = Put(RootNode, Newkey);
    
}

ARedBlackNode* ARedBlackBST::Put(ARedBlackNode* h, int NewKey)
{
    if (!h)
    {
        ARedBlackNode* NewNode = GetWorld()->SpawnActor<ARedBlackNode>(ARedBlackNode::StaticClass());
        NewNode->SetValue(NewKey, FakeValue);
        //新节点与父节点的联系一开始是红色的(后续可能会经过旋转,反转等操作,变成黑色)
        NewNode->SetColor(Red);
        NewNode->SetCount(1);
        return NewNode;
    }
    //与二叉搜索树相同,如果新节点的key比h节点的key小,则去h节点的左边;如果大,则去右边;如果相同,则覆盖h节点
    int Temp = CompareTo(NewKey, h->GetKey());
    //如果要插入新节点,则新节点的所有父节点都要更新一次
    if (Temp < 0) h->SetNode(true, Put(h->GetNode(true), NewKey));
    else if (Temp > 0) h->SetNode(false, Put(h->GetNode(false), NewKey));
    else h->SetValue(FakeValue);
    //更新h节点的Count
    h->SetCount(1 + Size(h->GetNode(true)) + Size(h->GetNode(false)));

    //h与右节点联系是红色的,且与左节点的联系是黑色的,则需要左旋
    if (IsRed(h->GetNode(false)) && !IsRed(h->GetNode(true))) h = RotateLeft(h);
    //如果h与左节点的联系是红色的,且h与左节点的左节点的联系也是红色的,说明出现连续两个红色联系,需要右旋
    if (IsRed(h->GetNode(true)) && IsRed(h->GetNode(true)->GetNode(true))) h = RotateRight(h);
    //如果h节点的左右联系都是红色,则需要反转(注意:根节点的反转只会把它们变为黑色,因为它没有父节点)
    if (IsRed(h->GetNode(true)) && IsRed(h->GetNode(false))) FlipColors(h);
    //以上三种情况排序是故意的,因为左旋后可能需要右旋,右旋后需要反转
    //以上三种情况可能会分别触发,也可能会连续触发
    
    return h;
}

  由于红黑树本质上是二叉搜索树,所以二叉搜索树的所有功能都可以在红黑树中使用。其中要特别注意的是 :

  1. 给定一个keyX,求有多少个节点的key比X小(即求Rank(X))

  2. 二叉搜索树的删除功能

   由于左旋和右旋都会改变节点的位置,所以进行左旋和右旋时,需要更新相关节点的Count(此节点下面共有多少个节点(包括自己))。如果每个节点的Count是准确的,那么求Rank(X)也能正常运行。

  二叉搜索树的删除功能在红黑树中虽然可以用,但会破坏红黑树的结构。红黑树有自己的一套删除功能,由于比较复杂,写在这会导致篇幅过长,因此写在下一篇随笔里。

  红黑树的其它功能代码实现基本上可以从二叉搜索树的代码中复制过来,这里不重复讲。具体实现可以从下面的完整代码中寻找,如果功能实现思路不理解的,可以重温一下二叉搜索树

5.节点的路线打印

  这个功能属于调试用功能,与红黑树的具体实现无关。

  由于程序输出的只是一个数组,我们怎么知道红黑树是否正确建立呢?

  为此,我在每个节点中,记录了它们的路线位置。如根节点为root;根节点的左节点为root->Left;根节点的左节点为root->Right;根节点的左节点的左节点为root->Left->Left。

  实现思路:每个节点的路线=父节点的路线+此节点是父节点的左节点还是右节点

  把整个红黑树按层次排序后,从上到下逐一更新路线。  

实现代码:

void ARedBlackBST::UpdateRouteString()
{
    RouteString = "";
    LevelOrderNodeArray.Empty();
    UpdateRouteString(RootNode);
}

void ARedBlackBST::UpdateRouteString(ARedBlackNode* X)
{
    TQueue<ARedBlackNode*> q;
    q.Enqueue(RootNode);
    while (!q.IsEmpty())
    {
        ARedBlackNode* T;
        q.Dequeue(T);
        LevelOrderNodeArray.Add(T);
        //如果T的父节点存在
        if (T->GetParent())
        {
            //如果T是左节点
            if (T == T->GetParent()->GetNode(true))
            {
                FString TempString;
                TempString.Append(T->GetParent()->GetValue());
                TempString.Append("->Left");
                T->SetValue(TempString);
            }
            //如果T是右节点
            else
            {
                FString TempString;
                TempString.Append(T->GetParent()->GetValue());
                TempString.Append("->Right");
                T->SetValue(TempString);
            }
        }
        //如果父节点不存在,说明是根节点
        else
        {
            T->SetValue("Root");
        }

        //将出队的左节点入队
        if (T->GetNode(true))
        {
            T->GetNode(true)->SetParent(T);
            q.Enqueue(T->GetNode(true));
        }
        //将出队的右节点入队
        if (T->GetNode(false))
        {
            T->GetNode(false)->SetParent(T);
            q.Enqueue(T->GetNode(false));
        }
    }
}

 6.完整的全部代码

节点.h:
    
UCLASS()
class ALGORITHM_API ARedBlackNode : public AActor
{
    GENERATED_BODY()
    
public:    
    // Sets default values for this actor's properties
    ARedBlackNode();
    // Called every frame
    virtual void Tick(float DeltaTime) override;
    //设值
    FORCEINLINE void SetValue(int Newkey, FString NewValue)
    {
        Key = Newkey;
        Value = NewValue;
    }

    FORCEINLINE ARedBlackNode* Get() { return this; }
    //获取或修改私有变量
    FORCEINLINE int GetKey() { return Key; }
    FORCEINLINE void SetKey(int NewKey) { Key = NewKey; }
    FORCEINLINE int GetCount() { return Count; }
    FORCEINLINE void SetCount(int NewCount) { Count = NewCount; }
    FORCEINLINE FString GetValue() { return Value; }
    FORCEINLINE void SetValue(FString NewValue) { Value = NewValue; }
    FORCEINLINE bool GetColor() { return Color; }
    FORCEINLINE void SetColor(bool IsRed) { Color = IsRed; }
    FORCEINLINE ARedBlackNode* GetParent() { return Parent; }
    FORCEINLINE void SetParent(ARedBlackNode* X) { Parent = X; }
    FORCEINLINE ARedBlackNode* GetNode(bool Left)
    {
        if (Left) return LeftNode;
        return RightNode;
    }
    FORCEINLINE void SetNode(bool Left, ARedBlackNode* NewNode)
    {
        if (Left)  LeftNode = NewNode;
        else
        {
            RightNode = NewNode;
        }
    }
    

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

private:
    int Key;
    FString Value;
    //左右节点
    ARedBlackNode* LeftNode;
    ARedBlackNode* RightNode;
    //父节点,这个节点是为了记录每个节点的位置(用于测试程序是否正确建立红黑树),与红黑树的实现无关。
    ARedBlackNode* Parent;
    //计算此节点下面共有多少个节点(包括自己)
    int Count;
    //与父节点之间的联系,如果为True,则是红色的;如果为False,则是黑色的
    bool Color;
    
};

红黑树.h:

class ARedBlackNode;

UCLASS()
class ALGORITHM_API ARedBlackBST : public AActor
{
    GENERATED_BODY()
    
public:    
    // Sets default values for this actor's properties
    ARedBlackBST();
    // Called every frame
    virtual void Tick(float DeltaTime) override;
    //判断一个节点和它父节点的联系是否是红色的
    bool IsRed(ARedBlackNode* X);
    //查值
    FString GetValue(int InputKey);
    //提供一个方法让TreeNode之间进行比较
    //如果a大于b,返回1;如果a小于b,返回-1;如果相等,返回0
    int CompareTo(int a, int b);
    //向左旋转
    //为什么要左旋:因为在红黑树中,所有红色的联系都是向左的。
    ARedBlackNode* RotateLeft(ARedBlackNode* h);
    //向右旋转
    //为什么要右旋:
    //如果出现连续两个红色联系时(即a,b,c是三个连续的节点,且ab,bc间的联系都是红色的),需要右旋一次
    //然后反转一次颜色,从而符合红黑树的游戏规则。
    ARedBlackNode* RotateRight(ARedBlackNode* h);
    //反转颜色:如果一个节点的左右联系都是红色,则将它们变为黑色,此节点与父节点的联系变为红色
    void FlipColors(ARedBlackNode* h);
    //插入一个节点
    void Put(int Newkey);
    ARedBlackNode* Put(ARedBlackNode* h, int NewKey);
    //中序遍历
    void InorderTraversal();
    void Inorder(ARedBlackNode* X);
    //寻找最小值
    int FindMin();
    //寻找拥有最小值的节点
    ARedBlackNode* FindMin(ARedBlackNode* X);
    //寻找最大值
    int FindMax();
    //寻找拥有最大值的节点
    ARedBlackNode* FindMax(ARedBlackNode* X);
    //给定一个数字,寻找最接近它的key(比它小)
    int FindFloor(int InputKey);
    ARedBlackNode* FindFloor(ARedBlackNode* X, int InputKey);
    //给定一个数字,寻找最接近它的key(比它大)
    int FindCeiling(int InputKey);
    ARedBlackNode* FindCeiling(ARedBlackNode* X, int InputKey);
    //
    //求有多少个数字少于给定数字
    int Size(ARedBlackNode* X);
    int Rank(int InputKey);
    int Rank(int InputKey, ARedBlackNode* X);
    //更新各节点路线(先进行层次排序,再更新路线)
    void UpdateRouteString();
    void UpdateRouteString(ARedBlackNode* X);


protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

private:

    //根节点
    ARedBlackNode* RootNode;
    //每个节点输入的值,可根据具体情况更改,这里只输入空格
    FString FakeValue;
    //把节点接过的路线记录下来,方便测试
    FString RouteString;
    //把节点按中序遍历放进数组
    TArray<ARedBlackNode*> OrderNodeArray;
    //把节点按层次遍历放进数组
    TArray<ARedBlackNode*> LevelOrderNodeArray;
};

红黑树.cpp:

//如果为True,则是红色的;如果为False,则是黑色的
bool Red = true;
bool Black = false;

// Sets default values
ARedBlackBST::ARedBlackBST()
{
     // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    FakeValue = "";
}

// Called when the game starts or when spawned
void ARedBlackBST::BeginPlay()
{
    Super::BeginPlay();
    FRandomStream Stream;
    Stream.GenerateNewSeed();
    //生成节点
    for (int i = 0; i < 100; i++)
    {
        Put(Stream.RandRange(0, 100));
    }
    
    Put(40);
    //中序排列
    InorderTraversal();
    //观察搜索树是否排列正确
    for (int i = 0; i < OrderNodeArray.Num(); i++)
    {
        UKismetSystemLibrary::PrintString(this, FString::FromInt(i) + ": "
            + FString::FromInt(OrderNodeArray[i]->GetKey())+" "+ OrderNodeArray[i]->GetValue());
    }
    //测试搜索和查值功能
    UKismetSystemLibrary::PrintString(this, "Find 40: " + GetValue(40));
    //测试寻找最小值、最大值、Floor、Ceiling
    UKismetSystemLibrary::PrintString(this, "Min: " + FString::FromInt(FindMin()) +
        " Max: " + FString::FromInt(FindMax()));
    UKismetSystemLibrary::PrintString(this, "Floor of 50: " + FString::FromInt(FindFloor(50)));
    UKismetSystemLibrary::PrintString(this, "Ceiling of 50: " + FString::FromInt(FindCeiling(50)));
    UKismetSystemLibrary::PrintString(this, "Rank(49): "+FString::FromInt(Rank(49)));
    UpdateRouteString();
    for (int i = 0; i < LevelOrderNodeArray.Num(); i++)
    {
        UKismetSystemLibrary::PrintString(this, FString::FromInt(i) + ": "
            + FString::FromInt(LevelOrderNodeArray[i]->GetKey()) + " " + LevelOrderNodeArray[i]->GetValue()
            + " Count: "+ FString::FromInt(LevelOrderNodeArray[i]->GetCount()));
    }
}

// Called every frame
void ARedBlackBST::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

//判断一个节点和它父节点的联系是否是红色的
bool ARedBlackBST::IsRed(ARedBlackNode* X)
{
    if (!X) return false;
    return X->GetColor() == Red;
}

FString ARedBlackBST::GetValue(int InputKey)
{
    ARedBlackNode* X = RootNode;
    while (X != nullptr)
    {
        //比较key的大小
        int Temp = CompareTo(InputKey, X->GetKey());
        //如果输入的key比X的小,去X的左边
        if (Temp < 0) X = X->GetNode(true);
        //如果输入的key比X的大,去X的右边
        else if (Temp > 0) X = X->GetNode(false);
        //如果相等,说明找到这个key了,输出Value
        else return X->GetValue();
    }
    //如果X为空指针,说明找不到这个key
    return "NotFind";
}

//如果a大于b,返回1;如果a小于b,返回-1;如果相等,返回0
int ARedBlackBST::CompareTo(int a, int b)
{
    if (a > b) return 1;
    else if (a < b) return -1;
    else return 0;
}

ARedBlackNode* ARedBlackBST::RotateLeft(ARedBlackNode* h)
{
    //X节点是h的右节点。
    //左旋的结果是:
    //1.X成为了h的父节点,h是X的左节点。
    //2.X原来的左节点变成了h的右节点,其它节点不变,h和X之间的联系还是红色。
    ARedBlackNode* X = h->GetNode(false);
    h->SetNode(false, X->GetNode(true));
    X->SetNode(true, h);
    X->SetColor(h->GetColor());
    h->SetColor(Red);
    //左旋后,两节点的Count更新
    h->SetCount(1 + Size(h->GetNode(false)) + Size(h->GetNode(true)));
    X->SetCount(1 + Size(X->GetNode(false)) + Size(X->GetNode(true)));
    UKismetSystemLibrary::PrintString(this, "RotateLeft");
    return X;
}

ARedBlackNode* ARedBlackBST::RotateRight(ARedBlackNode* h)
{
    //X节点是h的左节点。
    //左旋的结果是:
    //1.X成为了h的父节点,h是X的右节点。
    //2.X原来的右节点变成了h的左节点,其它节点不变,h和X之间的联系还是红色。
    ARedBlackNode* X = h->GetNode(true);
    h->SetNode(true, X->GetNode(false));
    X->SetNode(false, h);
    X->SetColor(h->GetColor());
    h->SetColor(Red);
    //右旋后,两节点的Count更新
    h->SetCount(1 + Size(h->GetNode(false)) + Size(h->GetNode(true)));
    X->SetCount(1 + Size(X->GetNode(false)) + Size(X->GetNode(true)));
    UKismetSystemLibrary::PrintString(this, "RotateRight");
    return X;
}

void ARedBlackBST::FlipColors(ARedBlackNode* h)
{
    //反转颜色:如果一个节点的左右联系都是红色,则将它们变为黑色,此节点与父节点的联系变为红色
    h->SetColor(Red);
    h->GetNode(true)->SetColor(Black);
    h->GetNode(false)->SetColor(Black);
    h->SetValue("");
}

void ARedBlackBST::Put(int Newkey)
{
    RootNode = Put(RootNode, Newkey);
    
}

ARedBlackNode* ARedBlackBST::Put(ARedBlackNode* h, int NewKey)
{
    if (!h)
    {
        ARedBlackNode* NewNode = GetWorld()->SpawnActor<ARedBlackNode>(ARedBlackNode::StaticClass());
        NewNode->SetValue(NewKey, FakeValue);
        //新节点与父节点的联系一开始是红色的(后续可能会经过旋转,反转等操作,变成黑色)
        NewNode->SetColor(Red);
        NewNode->SetCount(1);
        return NewNode;
    }
    //与二叉搜索树相同,如果新节点的key比h节点的key小,则去h节点的左边;如果大,则去右边;如果相同,则覆盖h节点
    int Temp = CompareTo(NewKey, h->GetKey());
    //如果要插入新节点,则新节点的所有父节点都要更新一次
    if (Temp < 0) h->SetNode(true, Put(h->GetNode(true), NewKey));
    else if (Temp > 0) h->SetNode(false, Put(h->GetNode(false), NewKey));
    else h->SetValue(FakeValue);
    //更新h节点的Count
    h->SetCount(1 + Size(h->GetNode(true)) + Size(h->GetNode(false)));

    //h与右节点联系是红色的,且与左节点的联系是黑色的,则需要左旋
    if (IsRed(h->GetNode(false)) && !IsRed(h->GetNode(true))) h = RotateLeft(h);
    //如果h与左节点的联系是红色的,且h与左节点的左节点的联系也是红色的,说明出现连续两个红色联系,需要右旋
    if (IsRed(h->GetNode(true)) && IsRed(h->GetNode(true)->GetNode(true))) h = RotateRight(h);
    //如果h节点的左右联系都是红色,则需要反转(注意:根节点的反转只会把它们变为黑色,因为它没有父节点)
    if (IsRed(h->GetNode(true)) && IsRed(h->GetNode(false))) FlipColors(h);
    //以上三种情况排序是故意的,因为左旋后可能需要右旋,右旋后需要反转
    //以上三种情况可能会分别触发,也可能会连续触发
    
    return h;
}

void ARedBlackBST::InorderTraversal()
{
    OrderNodeArray.Empty();
    Inorder(RootNode);
}

void ARedBlackBST::Inorder(ARedBlackNode* X)
{
    if (!X) return;
    //先去加X的左节点
    Inorder(X->GetNode(true));
    //再加X
    OrderNodeArray.Add(X);
    //最后加X的右节点
    Inorder(X->GetNode(false));
}

int ARedBlackBST::FindMin()
{
    //从根节点开始比较
    ARedBlackNode* X = FindMin(RootNode);
    if (X) return X->GetKey();
    return 0;
}

ARedBlackNode* ARedBlackBST::FindMin(ARedBlackNode* X)
{
    //当节点存在时
    while (X)
    {
        //如果左节点存在,继续循环
        if (X->GetNode(true))
        {
            X = X->GetNode(true);
        }
        //如果右节点不存在,这个节点就是最小值
        else
        {
            return X;
        }
    }
    return X;
}

int ARedBlackBST::FindMax()
{
    //从根节点开始比较
    ARedBlackNode* X = FindMax(RootNode);
    if (X) return X->GetKey();
    return 0;
}

ARedBlackNode* ARedBlackBST::FindMax(ARedBlackNode* X)
{
    //当节点存在时
    while (X)
    {
        //如果右节点存在,继续循环
        if (X->GetNode(false))
        {
            X = X->GetNode(false);
        }
        //如果右节点不存在,这个节点就是最小值
        else
        {
            return X;
        }
    }
    return X;
}

int ARedBlackBST::FindFloor(int InputKey)
{
    //从根节点开始比较
    ARedBlackNode* X = FindFloor(RootNode, InputKey);
    if (X) return X->GetKey();
    return 0;
}

ARedBlackNode* ARedBlackBST::FindFloor(ARedBlackNode* X, int InputKey)
{
    //如果X节点不存在,就别继续下去了
    if (!X) return nullptr;
    int Temp = CompareTo(InputKey, X->GetKey());
    //如果存在节点的key与输入值相等,则这个节点就是最接近它了
    if (Temp == 0) return X;
    //如果节点的key比较大,则去找它的左节点,直到找到小于等于输入值的节点为止
    if (Temp < 0) return FindFloor(X->GetNode(true), InputKey);
    //如果节点的key比较小,则要找的节点可能在它的右节点的左端
    ARedBlackNode* T = FindFloor(X->GetNode(false), InputKey);
    //如果找到了T,则说明找到了,返回T;如果找不到,说明X已经是最接近的了,返回X
    if (T) return T;
    else return X;
}

int ARedBlackBST::FindCeiling(int InputKey)
{
    //从根节点开始比较
    ARedBlackNode* X = FindCeiling(RootNode, InputKey);
    if (X) return X->GetKey();
    return 0;
}

ARedBlackNode* ARedBlackBST::FindCeiling(ARedBlackNode* X, int InputKey)
{
    //如果X节点不存在,就别继续下去了
    if (!X) return nullptr;
    int Temp = CompareTo(InputKey, X->GetKey());
    //如果存在节点的key与输入值相等,则这个节点就是最接近它了
    if (Temp == 0) return X;
    //如果节点的key比较小,则去找它的右节点,直到找到大于等于输入值的节点为止
    if (Temp > 0) return FindCeiling(X->GetNode(false), InputKey);
    //如果节点的key比较大,则要找的节点可能在它的左节点的左端
    ARedBlackNode* T = FindCeiling(X->GetNode(true), InputKey);
    //如果找到了T,则说明找到了,返回T;如果找不到,说明X已经是最接近的了,返回X
    if (T) return T;
    else return X;
}

int ARedBlackBST::Size(ARedBlackNode* X)
{
    //如果节点不存在,返回0
    if (!X) return 0;
    //如果节点存在,返回Count
    return X->GetCount();
}

int ARedBlackBST::Rank(int InputKey)
{
    return Rank(InputKey, RootNode);
}

int ARedBlackBST::Rank(int InputKey, ARedBlackNode* X)
{
    //如果节点不存在,返回0
    if (!X) return 0;
    int Temp = CompareTo(InputKey, X->GetKey());
    //如果给定数字比X的key小,则去X的左边去找比给定数字小的数字
    if (Temp < 0) return Rank(InputKey, X->GetNode(true));
    //如果给定数字比X的key大,则X和X的左节点都比给定数字小,把它们算上后,去X的右节点找是否还有比给定数字小的数字
    else if (Temp > 0) return 1 + Size(X->GetNode(true)) + Rank(InputKey, X->GetNode(false));
    //因为右节点都比X大,而X的Key与给定数字相等,故比给定数字小的数字都在X的左节点里
    else return Size(X->GetNode(true));
}

void ARedBlackBST::UpdateRouteString()
{
    RouteString = "";
    LevelOrderNodeArray.Empty();
    UpdateRouteString(RootNode);
}

void ARedBlackBST::UpdateRouteString(ARedBlackNode* X)
{
    TQueue<ARedBlackNode*> q;
    q.Enqueue(RootNode);
    while (!q.IsEmpty())
    {
        ARedBlackNode* T;
        q.Dequeue(T);
        LevelOrderNodeArray.Add(T);
        //如果T的父节点存在
        if (T->GetParent())
        {
            //如果T是左节点
            if (T == T->GetParent()->GetNode(true))
            {
                FString TempString;
                TempString.Append(T->GetParent()->GetValue());
                TempString.Append("->Left");
                T->SetValue(TempString);
            }
            //如果T是右节点
            else
            {
                FString TempString;
                TempString.Append(T->GetParent()->GetValue());
                TempString.Append("->Right");
                T->SetValue(TempString);
            }
        }
        //如果父节点不存在,说明是根节点
        else
        {
            T->SetValue("Root");
        }

        //将出队的左节点入队
        if (T->GetNode(true))
        {
            T->GetNode(true)->SetParent(T);
            q.Enqueue(T->GetNode(true));
        }
        //将出队的右节点入队
        if (T->GetNode(false))
        {
            T->GetNode(false)->SetParent(T);
            q.Enqueue(T->GetNode(false));
        }
    }
}

  

  

猜你喜欢

转载自www.cnblogs.com/mcomco/p/10195046.html