上机实践报告
一、目的
1.熟悉算法设计的基本思想
2.掌握构建红黑树的方法
二、内容与设计思想
编写红黑树构建算法,中序遍历各节点,输出颜色和值;
使用红黑树构建算法,并画图描述不同情况下的运行时间差异;
三、使用环境
推荐使用C/C++集成编译环境。
[oj | 《算法设计与分析》第四次实验课](http://47.100.233.213/contest/87) (密码:40088860) |
四、实验过程
1. 写出红黑树构建算法的源代码
#include <bits/stdc++.h>
using namespace std;
#define RED 0
#define BLACK 1
//定义红黑树结点
struct Node
{
char color; //颜色
int key; //值
Node *lchild; //左孩子
Node *rchild; //右孩子
Node *parent; //父结点
};
class RBtree
{
public:
//新建一个结点
Node *creat_root(int key, Node *parent, Node *lchild, Node *rchild)
{
Node *p;
p = new Node();
p->key = key;
p->lchild = lchild;
p->rchild = rchild;
p->color = BLACK;
return p;
}
//左旋——将该结点变成其右孩子的左孩子
void rbtree_left_rotate(Node *&root, Node *x)
{
Node *y = x->rchild; //设置x的右结点等于y
//首先,先找到y的左孩子,它最终被x收养为右孩子
x->rchild = y->lchild;
if (y->lchild != NULL)
y->lchild->parent = x;
//将x的父亲设为y的父亲
y->parent = x->parent;
if (x->parent == NULL) //当x为根结点的时候
{
root = y; //将y设为根结点
}
else //当x不是根节点的时候
{
if (x->parent->lchild == x)
{
x->parent->lchild = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
}
else
{
x->parent->rchild = y; // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
}
}
y->lchild = x; //x就位
x->parent = y;
//printf("(对关键字%d进行左旋)",x->key);
}
//右旋
void rbtree_right_rotate(Node *&root, Node *y)
{
Node *x = y->lchild;
y->lchild = x->rchild;
//找到x的右孩子,它最终被y收养为左孩子
if (x->rchild != NULL)
{
x->rchild->parent = y;
}
x->parent = y->parent;
//此时x的右孩子是空的,y来当x的右孩子
if (y->parent == NULL) //如果y为根结点
{
root = x; //将x设为根节点
}
else
{
if (y->parent->rchild == y) //要确定x是做的左孩子还是右孩子
{
y->parent->rchild = x;
}
else
{
y->parent->lchild = x;
}
}
x->rchild = y; //y就位
y->parent = x;
}
//插入修正 —— 先向该树直接插入一个结点,设置为红色,为了不影响性质4,再进行调整
void rbtree_insert_fixup(Node *&root, Node *node)
{
Node *parent, *gparent;
// 若父节点存在,并且父节点的颜色是红色
while ((parent = node->parent) && (parent->color == RED))
{
gparent = parent->parent;
//若“父节点”是“祖父节点的左孩子”
if (parent == gparent->lchild)
{
// Case 1条件:叔叔节点是红色
{
Node *uncle = gparent->rchild;
if (uncle && uncle->color == RED)
{ //父、叔变黑,爷变红,对爷进行判断
uncle->color = BLACK;
parent->color = BLACK;
gparent->color = RED;
node = gparent;
continue;
}
}
// Case 2条件:叔叔是黑色,且当前节点是右孩子
if (parent->rchild == node)
{
Node *tmp;
rbtree_left_rotate(root, parent); //父左旋
tmp = parent;
parent = node;
node = tmp;
}
// Case 3条件:叔叔是黑色,且当前节点是左孩子。
parent->color = BLACK;
gparent->color = RED;
rbtree_right_rotate(root, gparent);
}
else //若“z的父节点”是“z的祖父节点的右孩子”
{
// Case 1条件:叔叔节点是红色
{
Node *uncle = gparent->lchild;
if (uncle && (uncle->color == RED))
{
uncle->color = BLACK;
parent->color = BLACK;
gparent->color = RED;
node = gparent;
continue;
}
}
// Case 2条件:叔叔是黑色,且当前节点是左孩子
if (parent->lchild == node)
{
Node *tmp;
rbtree_right_rotate(root, parent);
tmp = parent;
parent = node;
node = tmp;
}
// Case 3条件:叔叔是黑色,且当前节点是右孩子。
parent->color = BLACK;
gparent->color = RED;
rbtree_left_rotate(root, gparent);
}
}
// 将根节点设为黑色
root->color = BLACK;
}
//插入
void rbtree_insert(Node *&root, Node *node)
{
Node *y = NULL;
Node *x = root;
while (x != NULL) //x为叶子结点跳出循环
{
y = x;
if (x->key > node->key)
{
x = x->lchild;
}
else
{
x = x->rchild;
}
}
node->parent = y;
if (y != NULL)
{
if (node->key < y->key)
{
y->lchild = node;
}
else
{
y->rchild = node;
}
}
else
{
root = node; //若y为NULL,说明树为空,则将node设为根节点
}
node->color = RED; //将颜色设为红色
//插入修正
rbtree_insert_fixup(root, node);
}
int insert_rbtree(Node *&root, int key)
{
Node *node; //新建一个结点
node = creat_root(key, NULL, NULL, NULL);
if (node == NULL)
return -1;
else
rbtree_insert(root, node);
return 0;
}
//中序遍历"红黑树"
void inorder(Node *root)
{
if (root != NULL)
{
inorder(root->lchild);
//cout << root->key<<" ";
if (root->color == 0)
{
cout << "R";
}
else
{
cout << "B";
}
inorder(root->rchild);
}
}
};
int main()
{
int N;
cin >> N;
int a[N];
RBtree mytree;
//下面开始创建红黑树
Node *root = new Node();
cin >> a[0];
root = mytree.creat_root(a[0], NULL, NULL, NULL);
//然后插入
for (int i = 1; i < N; i++)
{
cin >> a[i];
mytree.insert_rbtree(root, a[i]);
}
mytree.inorder(root);
return 0;
}
2. 分别画出各个实验结果的折线图
五、总结
由实践结果——图表可以看出,随着数据规模即节点数增大,运行时间也随着增加。这次红黑树的实验主要是回归算法本身,该实验主要用到函数就是rbtree_insert(插入)和rbtree_insert_fixup(调整),rbtree_insert主要是为了得到一颗二叉查找树,rbtree_insert_fixup是为了使其满足红黑树的性质,成为红黑树,在其中还用到了左旋和右旋函数。由于一颗有n个结点的红黑树的高度为O(lgn),所以在rbtree_insert函数最后一步之前,要花费O(lgn)时间;在rbtree_insert_fixup函数中,仅当情况1发生后,node指针会沿着树上升两层,while循环才会重复执行。所以while循环可能被执行的总次数为O(lgn)。因此,rbtree_insert总共花费O(lgn)的时间。此外,该程序的旋转从不超过2次,因为只执行了情况2或情况3,while循环就结束了。由算法原理可以看出实验结果是符合的。
自己一开始运行一直不通过出错的原因在于,当遇到情况三也就是在右旋之前,忘记先把当前节点的父结点由红变为黑色,祖父结点由黑变为红色,所以运行结果一直不正确。通过这次实验是自己对红黑树有了更深的理解。
关于算法优化方面,在网上看到了一个优化思路为:
书上思路为从根到叶子进行遍历,寻找到插入位置,直接将新数据染红并插入,最后进行调整以符合红黑树性质。调整分两大类:1是父和叔均红,解决方案为将祖父由黑变红,父和叔由红变黑,然后将祖父结点作为新的问题,也就是将问题上移两层,最坏情况递归到根才能解决。2是叔为黑,此时可通过最多两次旋转直接解决。 因为在查找插入位置时,要从根到叶子进行遍历,如果此时检查路径上是否存在两个孩子均为红的情况并解决,则在FIXUP的函数中可以避免父和叔均红的情况,使得FIXUP只需要通过O(1)的时间即可完成。
但这样修改后我发现时间运行并没有很大差别,修改后虽然省去了向上递归的消耗,但旋转的次数也增多了。
文档信息
- 本文作者:Liu Jiafan
- 本文链接:https://jiapang777777.github.io//2023/04/29/%E7%BA%A2%E9%BB%91%E6%A0%91/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)