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

被折叠的 条评论
为什么被折叠?



