红黑树的算法图示(基于HTML)

本文详细介绍了红黑树的插入操作过程,包括四种情况和对应的调整策略,如颜色翻转和旋转操作。通过示例展示了插入新节点后如何保持红黑树的性质,以及实际操作中对树的序列化和反序列化方法。同时,提供了一个用于可视化的HTML页面,用于展示不同插入操作后的红黑树结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 网页端的最终效果

 

新节点的父亲

是爷爷的

左孩子

新结点的爷爷的右孩子,即新节点的右叔叔,是红色结点

我是父亲的左孩子

或右孩子不重要了

(父亲_红,

叔叔_红,

我_红)

1(红,红,X红)

右叔叔是黑色结点

我是父亲的右孩子

(父亲_红,

叔叔_黑,

我_右红)

2(红,黑,右红)

右叔叔是黑色结点

我是父亲的左孩子

(父亲_红,

叔叔_黑,

我_右红)

3(红,黑,左红)

新节点的父亲

是爷爷的

右孩子

新结点的爷爷的左孩子,即新节点的左叔叔,是红色结点

我是父亲的左孩子

或右孩子不重要了

(叔叔_红,

父亲_红,

我_红

4(红,红,X红)

左叔叔是黑色结点

我是父亲的右孩子

(叔叔_黑,

父亲_红,

我_右红

5(黑,红,右红)

左叔叔是黑色结点

我是父亲的左孩子

(叔叔_黑,

父亲_红,

我_左红

6(黑,红,左红)

插入新结点4后,(5,8,4),构成情况1(红,红,X红);

第一次调整后,(2,14,7),构成情况2(红,黑,右红);

第二次调整后,(7,14,2),构成情况3(红,黑,左红);

第三次调整后,合法。

2 操作过程

(1) 1. 两数之和 - 力扣(LeetCode) (leetcode-cn.com)

打开链接,复制3中leetcode的源代码,并执行

(2)新建RBTree.html

复制3中html的源代码

并复制leetcode,stdout的数组,替换RBTree.html中RawTrees数组中的数据

用网页打开RBTree.html

(3)若想尝试其他示例,请修改leetcode中的代码

      //序列化的RBTree
      string RawInput=
       "11 1 1 2 2 0 3 4 14 1 -1 5 1 1 -1 -1 7 1 6 7 15 0 -1 -1 5 0 -1 -1 8 0 -1 -1 ";
  

      //NewNode
      MyRBTree.Insert(4);

    基于不同的树,插入不同的结点,最终产生的html的展示效果是不同的

    leetcode 算出单次插入操作中,产生的四棵树;

    新建html,数据到html脚本,并用网页打开。

(4)等后续可以租一个服务器,后台计算数据,前台展示红黑树即可。

3 源代码

//红黑树
#define Debug 0
 enum Color
 {
   RED=0,
   BLACK=1
 };
struct Node
{
    Node(int n=0,Color c=BLACK,Node * pl=nullptr,Node * pr=nullptr,Node * pp=nullptr)
    :key(n),color(c),l(pl),r(pr),p(pp){}
    Node *l,*r,*p;
    int key;
    Color color;
};
ostream& operator<<(ostream& os,Node& node)
{
  os<<node.key<<(node.color==RED?"红":"黑")<<'('<<node.p->key<<','<<node.l->key<<','<<node.r->key<<')';
  return os;
}

class RBTree
{
  public:
  //反序列化
  RBTree(string RawInput)
  {
    //字符形式的树->数组InputArr
    istringstream istrm(RawInput);
    string str;
    vector<array<int,4>> InputArr;
    array<int,4> temp;
    int i=0;
    while(istrm>>str)
    {
       temp[i++]=stoi(str);
       if(i==4)
       {
         i=0;
         InputArr.push_back(temp);
       }
    }
    //数组InputArr->结点数组
    vector<Node *>PArr(InputArr.size(),nullptr);
    for(i=0;i<PArr.size();i++)
    {
      PArr[i]=new Node(InputArr[i][0],InputArr[i][1]?BLACK:RED);
    }
    nil=new Node(-1);
    root=PArr[0];
    PArr[0]->p=nil;
    for(i=0;i<PArr.size();i++)
    {
      if(InputArr[i][2]==-1)
      PArr[i]->l=nil;
      else 
      {
       PArr[i]->l=PArr[InputArr[i][2]];
       PArr[InputArr[i][2]]->p=PArr[i];
      }
      
      if(InputArr[i][3]==-1)
      PArr[i]->r=nil;
      else 
      {
       PArr[i]->r=PArr[InputArr[i][3]];
       PArr[InputArr[i][3]]->p=PArr[i];
      }
    }
    #if Debug
    for(i=0;i<PArr.size();i++)
    {
      cout<<*PArr[i]<<endl;
    }
    #endif

    //PArr会销毁,以后只能靠root遍历树
  }
  //序列化
  string  Serialize()
  {
    string str;
    //层次序遍历,出一个,进一层
    queue<Node *> q;
    q.push(root);
    Node * temp;
    int index=1;
    while(!q.empty())
    {
      temp=q.front();
      q.pop();
      str+=to_string(temp->key)+' ';
      if(temp->color==RED)
      str+="0 ";
      else
      str+="1 ";
      if(temp->l!=nil)
      {
        str+=to_string(index)+' ';
        index++;
        q.push(temp->l);
      }
      else
      {
        str+=to_string(-1)+' ';
      }
      if(temp->r!=nil)
      {
        str+=to_string(index)+' ';
        index++;
        q.push(temp->r);
      }
      else
      {
        str+=to_string(-1)+' ';
      }
    }
    return str;
  }
  //插入新节点,会破坏红黑树的规则,需要调整,共有6种情况
  bool Insert(int n)
  {
    //两种路径都需要测试:从默构开始插入第一个新节点;从反序列化开始插入新节点
    //非空树:y是x的爹
    //空树,默构:nil=new Node(-1);root=nullptr
    Node *y=nil;
    Node *x=root;
    while(x!=nil)
    {
      y=x;
      if(n<x->key) x=x->l;
      else if(n>x->key) x=x->r;
      else return false;
    }
    //y=nil(空树)/叶节点,都是新节点的 爹
    Node * z=new Node(n);
    z->p=y;
    //第一段相互的,在nil和新节点;
    //第二段相互的,在新节点和nil
    if(y==nil)
    {
      root=z;
    }
    //第一段相互的,在叶节点和新节点;
    //第一段相互的,在新节点和nil;
    else if(n<y->key)
    {
      y->l=z;
    }
    else if(n>y->key)
    {
      y->r=z;
    }
    z->l=nil;
    z->r=nil; 
    //只有一棵树时,红结点,后面再看两个结点红红的调整
    z->color=RED;
    
    int CaseIndex=1;
    //打印初始的违规树,即情况1
    //cout<<"RawInput"<<CaseIndex++<<"=\""<<this->Serialize()<<"\""<<endl;
    cout<<"\""<<this->Serialize()<<"\","<<endl;


    //连续的黑可以
    //连续的红不可以
    //红红
    while(z->p->color==RED)
    {
      //父亲是爷爷的左孩子 (父亲红,叔叔红/黑,我红)
      if(z->p==z->p->p->l)
      {
        //右叔叔
        y=z->p->p->r;
        //情况一,(红,黑,X红),我是父亲的哪种孩子已经不重要了
        if(y->color==RED)
        {
          z->p->color=BLACK;y->color=BLACK;z->p->p->color=RED;
          z=z->p->p;
        }
        //情况二,叔叔是黑的,但是我是父亲的右孩子 (红,黑,右红)
        else if(z==z->p->r)
        {
          z=z->p;
          LeftRotate(z);//左转父亲,逆子翻身
        }
          
        //情况三,叔叔是黑的,但是我是父亲的左孩子(红,黑,左红)
        else if(z==z->p->l)
        {
          z->p->color=BLACK;z->p->p->color=RED;
          RightRotate(z->p->p);//右转爷爷,父亲翻身
        }
      }
      //父亲是爷爷的右孩子
      else
      {

      }

      //第一次调整后,树由情况1,变成情况2
      //cout<<"RawInput"<<CaseIndex++<<"=\""<<this->Serialize()<<"\""<<endl;
      cout<<"\""<<this->Serialize()<<"\","<<endl;
    }
    
    return true;
  }
  private:
  Node *root,*nil;
  void LeftRotate(Node * x)
  {
    Node *y=x->r;
    //一条腿
    x->r=y->l;if(y->l!=nil) y->l->p=x;
    //最上层
    y->p=x->p;if(x->p==nil) root=y;else if(x==x->p->l)x->p->l=y;else x->p->r=y;
    //中间那条腿
    y->l=x;x->p=y;
  }
  void RightRotate(Node * y)
  {
    Node *x=y->l;
    //一条腿,yb之间
    y->l=x->r;if(x->r!=nil) x->r->p=y;
    //最上层,x和y父亲之间
    x->p=y->p;if(y->p==nil) root=x;else if(y==y->p->l)y->p->l=x;else y->p->r=x;
    //中间那条腿xy之间
    x->r=y;y->p=x;
  }
};
 

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
      //序列化的RBTree(硬盘上)(11 1 1 2)->(数据11,0_RED;1_BLACK,左子女结点是RawInput的第几个,右子女结点是RawInput的第几个)
      //序列化变成方案1:数据11,存放两个字符'1''1';定长方案2:int是4个字节的,存放固定的4个字符
      string RawInput=
       "11 1 1 2 2 0 3 4 14 1 -1 5 1 1 -1 -1 7 1 6 7 15 0 -1 -1 5 0 -1 -1 8 0 -1 -1 ";
      //反序列化得到初始的红黑树(内存中) 
      RBTree MyRBTree(RawInput);
      //NewNode
      MyRBTree.Insert(4);
      
      vector<int> laji;
      return laji;
    }
};

<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>红黑树</title> 
</head>
<body>

<canvas id="myCanvas" width="200" height="100" style="border:1px solid #c3c3c3;background-color: azure;">
</canvas>

<script>
/*-------------------------------------------类与函数---------------------------------------------------------*/
class Node {
  constructor(key, color,l,r) {
    this.key = key;
    this.color = color;
    this.l=l;
    this.r=r;
  }
}
function PreOrder(tree,index,x,y)
{
    //结点
    if(tree[index].color=="RED")
    ctx.fillStyle="red";
    else
    ctx.fillStyle="black";
    ctx.beginPath();
    ctx.arc(x,y,NodeRadius,Math.PI*2,0,true);
    ctx.closePath();
    ctx.fill();
    //数据域
    ctx.fillStyle="white";
    ctx.font=TestSize+"px Arial";
    NodeStr=""
    NodeStr+=tree[index].key
    if(tree[index].key<10)
    ctx.fillText(NodeStr,x-TestSize/3,y+TestSize/2);
    else 
    ctx.fillText(NodeStr,x-TestSize/2,y+TestSize/2);
    if(index==0)
    {
        //左子树
        if(tree[index].l!=-1)
        {
        //画指针
        ctx.moveTo(x-NodeRadius/1.414,y+NodeRadius/1.414);
        ctx.lineTo(x-XOffSet-ZeroOffSet+NodeRadius/1.414,y+YOffSet-NodeRadius/1.414);
        ctx.stroke();   
        PreOrder(tree,tree[index].l,x-XOffSet-ZeroOffSet,y+YOffSet); 
        }
        //右子树
        if(tree[index].r!=-1)
        {
        //画指针
        ctx.moveTo(x+NodeRadius/1.414,y+NodeRadius/1.414);
        ctx.lineTo(x+XOffSet+ZeroOffSet-NodeRadius/1.414,y+YOffSet-NodeRadius/1.414);
        ctx.stroke();  
        PreOrder(tree,tree[index].r,x+XOffSet+ZeroOffSet,y+YOffSet); 
        }
    }
    else
    {
        //左子树
        if(tree[index].l!=-1)
        {
        //画指针
        ctx.moveTo(x-NodeRadius/1.414,y+NodeRadius/1.414);
        ctx.lineTo(x-XOffSet+NodeRadius/1.414,y+YOffSet-NodeRadius/1.414);
        ctx.stroke();   
        PreOrder(tree,tree[index].l,x-XOffSet,y+YOffSet); 
        }
        //右子树
        if(tree[index].r!=-1)
        {
        //画指针
        ctx.moveTo(x+NodeRadius/1.414,y+NodeRadius/1.414);
        ctx.lineTo(x+XOffSet-NodeRadius/1.414,y+YOffSet-NodeRadius/1.414);
        ctx.stroke();  
        PreOrder(tree,tree[index].r,x+XOffSet,y+YOffSet); 
        }
    }
}




/*-------------------------------------------变量---------------------------------------------------------*/
var RawTrees=
[
"11 1 1 2 2 0 3 4 14 1 -1 5 1 1 -1 -1 7 1 6 7 15 0 -1 -1 5 0 8 -1 8 0 -1 -1 4 0 -1 -1 ",
"11 1 1 2 2 0 3 4 14 1 -1 5 1 1 -1 -1 7 0 6 7 15 0 -1 -1 5 1 8 -1 8 1 -1 -1 4 0 -1 -1 ",
"11 1 1 2 7 0 3 4 14 1 -1 5 2 0 6 7 8 1 -1 -1 15 0 -1 -1 1 1 -1 -1 5 1 8 -1 4 0 -1 -1 ",
"7 1 1 2 2 0 3 4 11 0 5 6 1 1 -1 -1 5 1 7 -1 8 1 -1 -1 14 1 -1 8 4 0 -1 -1 15 0 -1 -1 "
]
var RBTrees=new Array();
var TreeNum=RawTrees.length
var node=new Node(0,'BLACK',-1,-1),NodeIndex=0


var TestSize=10,NodeRadius=20
var XOffSet=60, YOffSet=60,ZeroOffSet=100

var i=0,j=0,k=0
var str=""

var CanvasWidth=600*(TreeNum+1),CanvasHeight=600
var c=document.getElementById("myCanvas"),ctx=c.getContext("2d");

/*-------------------------------------------do sth---------------------------------------------------------*/
c.setAttribute("width",CanvasWidth);c.setAttribute("height",CanvasHeight)

for(i=0;i<TreeNum;i++)
{
    RBTrees[i]=new Array();
    str=""
    k=0
    NodeIndex=0
    for(j=0;j<RawTrees[i].length;j++)
    {
        if(RawTrees[i][j]!=' ')
        {
            str+=RawTrees[i][j]
        }
        else
        {
            if(k==0)
            node.key=parseInt(str)
            else if(k==1)
            {
                if(str=='0')
                node.color='RED'
                else
                node.color='BLACK'
            }
            else if(k==2)
            {
                node.l=parseInt(str)
            }
            else if(k=3)
            {
                node.r=parseInt(str)
            }
            k++
            str=""
            if(k==4)
            {
                k=0
                RBTrees[i][NodeIndex]=new Node(node.key,node.color,node.l,node.r)
                NodeIndex++
            }
        }
    }
    PreOrder(RBTrees[i],0,600*(i+1),300)
}


</script>

</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值