1.熟悉算法设计的基本思想
2.掌握构建红黑树的方法
- 编写红黑树构建算法,中序遍历各节点,输出颜色和值;
- 使用红黑树构建算法,并画图描述不同情况下的运行时间差异;
细节实现
- 需要通过输入插入建树,其中需要结点结构、实现二叉搜索树遍历查找、插入后的分情况处理与结点旋转。
- 输出时需要一个中序遍历函数。
- 已知红黑树的的特点,建树的时候要注意每次插入新节点都要保持其五个特性。
一棵红黑树是满足下面红黑性质的二叉搜索树:
- 各个结点或者是红色,或者是黑色的.
- 根结点是黑色的.
- 每个叶结点 (NIL) 是黑色的.
- 如果一个结点是红色,则它的两个子结点都是黑色的.
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点.
-
- 建树由插入新结点开始,即需寻找插入结点的规律使五个特性受到最小程度的破坏 。类似二叉搜索树找到新结点应存在的位置,再将其颜色设为红色’R’(若其颜色设为黑色,每次插入均会违反性质4;而插入颜色为红时,有可能不违反。)
-
- 插入新结点后存在的情况:
-
父结点为黑色,则未违反红黑树性质,不需调整。
-
父结点为红色:其中需调整只会影响到向上三层,也可理解为三层三层调整。其中祖结点为黑色,父结点与当前结点为红色,则不同情况共分为 当前结点为左或右、叔结点为红或黑,父结点为左或右分别叠加为8种。
其中当叔结点为红色时,只需重新设置颜色上移,此时当前结点为左或右无区别。
其中 当其他状况相同,只有父结点在左或右的区别时为对称状态,只需将左右对换。
因此,只需考虑3种情况: 设当前结点为z,当前结点的叔结点为y。1. z的父结点为左结点,叔结点y为红色,将不符的性质上移调整。
z.p.color=B;
y.color=B;
z.p.p.color=R;
z=z.p.p;
2. z的父结点是左结点,叔结点y为黑色,z为左孩子。进行调整:
z.p.color=B;
z.p.p.color=R;
Right_Rotate(z.p.p);3. z的父结点为左结点,叔结点为黑色,z为右孩子。只需将其调整为情况二,再按照情况二的调整方式调整。
z=z.p;
LEFT-Rotate(z);
【当z的父结点为右结点时,由于对称,将上述的所有right改为left,所有left改为right】
- 根据了解红黑树,可写出红黑树结点的结构
typedef struct Tree { int data; char color; Tree*p; Tree*left; Tree*right; } - 以x为支点左旋 与 以y为支点右旋
均为分四步:结点之间拆分与连接 (右旋类似)
#includeusing namespace std; typedef struct Tree //红黑树结构 { int data; char color; Tree *left; Tree *right; Tree *parent; } * Node; Node root = nullptr; //记录根中内容 void Left_rotate(Node x) //以x为支点左旋 { Node y = x->right; x->right=y->left; if(y->left!=nullptr) y->left->parent = x; y->parent = x->parent; if (x->parent == nullptr) { root = y; }else if(x==x->parent->left) { x->parent->left = y; } else { x->parent->right = y; } y->left = x; x->parent = y; } void Right_rotate(Node x) //以x为支点右旋 { Node y = x->left; x->left = y->right; if(y->right!=nullptr) y->right->parent = x; y->parent = x->parent; if(x->parent==nullptr) { root = y; }else if (x==x->parent->right) { x->parent->right=y; }else { x->parent->left=y; } y->right = x; x->parent = y; } void Fixnode(Node x) //调整节点位置 { while ((x->parent!=nullptr) && (x->parent->color == 'R')) { Node grandparent = x->parent->parent; if (x->parent == grandparent->left) //父结点为祖父节点的左孩子 { if (grandparent->right == nullptr || grandparent->right->color == 'B') //叔结点为黑色 { if (x == x->parent->right) //当前节点为右节点---------------> case 1 { x = x->parent; Left_rotate(x); } x->parent->color = 'B'; //当前节点为左节点----------------> case 2 grandparent->color = 'R'; Right_rotate(grandparent); } else //叔结点为红色-------------> case 3 { x->parent->color = 'B'; grandparent->right->color = 'B'; grandparent->color = 'R'; x = grandparent; } } else if (x->parent == grandparent->right) //父结点为祖父节点的右孩子(与前三种情况对称) { if (grandparent->left == nullptr || grandparent->left->color == 'B') //叔结点为黑色 { if (x == x->parent->left) //当前节点为左节点------------->case 4 { x = x->parent; Right_rotate(x); } x->parent->color = 'B';//当前节点为右节点------------> case 5 grandparent->color = 'R'; Left_rotate(grandparent); } else //叔结点为红色---------------->case 6 { x->parent->color = 'B'; grandparent->left->color = 'B'; grandparent->color = 'R'; x = grandparent; } } } root->color = 'B'; } void Insertnode(Node r, Node z) //找到位置插入结点 { Node y = nullptr; //找到新节点的父亲 Node x = r; while (x != nullptr) { y = x; //记录x的上一个 if (z->data < x->data) { x = x->left; } else { x = x->right; } } z->parent = y; if (y == nullptr) //当父结点为空时,新插入节点为根 { root = z; } else if (z->data < y->data) //小于插在左边 { y->left = z; } else //大于插在右边 { y->right = z; } z->left = nullptr; z->right = nullptr; z->color = 'R'; Fixnode(z); } void print(Node r) //中序输出结点颜色 { if (r != nullptr) { print(r->left); cout << r->color; print(r->right); } } int main() { int N; int x; cin >> N; for (int i = 0; i < N; i++) { cin >> x; Node n = new Tree; n->data = x; Insertnode(root, n); } print(root); return 0; }
左右旋的代码有一个最后检查 空指针的情况。下次使用树的指针时一定要注意避免空指针的访问问题。
根据理论计算,红黑树算法查找的时间复杂度应该为 O(logn),该实验测试的时间包括查找与插入建树与遍历总时间,所以通过插入方式构建元素个数为 N 的红黑树时,时间复杂度 O(log((n-1)!)),再经过遍历时间复杂度为 O(n).如图。
2.分别画出各个实验结果的折线图


