计算机中的红与黑(一)
看到题目大家肯定会很吃惊,怎么计算机可以和《红与黑》联系到一起,难当司汤达笔下的于连通过经营电脑生意而发财?哈哈……。不开玩笑了,实际上和那个充满文学烂漫气息的《红与黑》一点关系也没有,这篇博文的目的是帮助笔者缕清最近在数据结构红黑树中的一些理解,以备将来之需!
一、红黑树
红黑树诞生于1972年,当时叫做平衡二叉B树,后来在1978年改称为红黑树。红黑树本质上是一种二叉搜索树,它的最大特点是适合在动态的数据集合上进行相应的操作,这里的操作包括了search,delete,insert操作等等。众所周知,二叉查找在一个固定的数组上进行操作时速度回非常的快,速度可以达到log(n),如何在链表上实现类似的操作?我们知道在链表上实现这样的操作的一个好处是可以非常好的支持插入和删除,因为不会面临在数组当中需要移动大量数据的情况。(关于二叉查找树的产生,我不是很清楚,好像在链表上也可以实现log(n)的查找,但是数据碎片确实是一个数组很难解决的问题,好像在leetcode当中有一个题来着)
红黑树的特点是使用每一条从根到子节点的路径上黑节点的个数来控制数的高度,使整棵树近似于平衡状态。红黑树中每个节点的表示包括了五个域,分别为:color,key,left,right,p。如果某个节点的left域,right域或者f域为空则,设置其指向一个某节点,为了方便边界检查,这里设置这些空节点为一个相同的黑节点NIL。如下所示为一棵使用了NIL节点的红黑树。
class Node{ public: int value; int key; Node * left; Node * right; Node * father; }
红黑树的基本性质有五点:
1、所以节点不是红色就是黑色
2、根节点必须是黑色
3、NIL节点为黑色节点
4、不能出现两个连续的红色节点,也就是如果一个节点为红色,则其子孙节点都必须是黑色
5、从红黑树中每一个节点,到其子孙节点的所有路径上的黑色节点的个数相等
派生性质:
1、从上面性质的4,5我们可以得出,对于任意一个节点的左右子树,高度最多相差一倍。考虑可能发生的极端情况,一棵子树为红黑交错,另一棵子树全为黑色。
2、一棵有n个节点的红黑树的高度最高为2*log(n+1)
二、二叉查找树
所谓二叉查找树是一棵能够用于查找的树。每一个节点包括了两个域,一个是key,另一个为value(卫星数据)。任意一个节点的key值要大于其左子结点的key值,小于其右子节点的key值。
这样的结构非常便于在子树当中进行搜索,尤其当数的结构为近似平衡状态时,搜索的性能为在数组上的二叉查找的性能是一样的,例如AVL树等等。这里我们考虑的二叉查找树为普通的二叉查找树,也就是我们不考虑树的平衡性问题。我们在这里主要考虑二叉查找树的insert操作和delete操作。
1、insert
insert的操作过程有点类似search的过程,我们只有在search过程当中没有查找到节点时,才会进行插入。从二叉查找树的根节点开始,算法检查要查找的key是否与该节点的key相等,如果相等,则该节点在树中已经存在,则无需插入。如果小于该节点,在从该节点的左子结点,继续查找。如果大于该节点,则从该节点的右子节点,继续查找。最后我们一定会找到一个空的节点,这个空节点的位置,就是新的节点要插入的位置。
下面伪代码:
boolean insert(Node * root,Node * newnode){ int key=newnode->key; Node * f=root->f;// to record the father node Node * y=root;// the node itself while(y!=NULL){ f=y; if(y->key<key){ y=f->left; }else if(y->key>key){ y=f->right; }else{ return false; } } if(f==nil){//there is no element in the tree now Troot=newnode; }else{ if(f.right==y) f->right=newnode; else f->left=newnode; } newnode->right=NIL; newnode->left=NIL; return true; }
2、delete操作
在二叉树中进行delete,主要是考虑下面三种情况,假设要删除的节点为x
1)x没有孩子节点,也就是x是一个叶节点,则直接删除,并且改变x的父节点的孩子指针
2)x有一个孩子节点,对于这种情况,我们只需要修改x的父节点的孩子指针指向这个唯一的孩子即可。可以证明这是正确的。
证明:假设这个唯一的孩子为child,x的父节点为father
首先以child为根的子树的二叉树的性质是保持的。我们考虑将其设置为father的孩子之后,整棵树的性质是否还保持。
情况1:如果x为father的左孩子节点,则x所在子树的所有节点的key小于father,所以child中的所有key小于father的key,将child设置为father的左子树之后,二叉树性质保持
情况2:如果x为father的右孩子节点,则x所在子树的所有节点的key大于father,所有child中的所有key大于father的key,将child设置为father的右子树之后,二叉树性质保持
3)x有两个孩子节点,对于这种情况,进行上面两种情况的简单的连接是不可能的。
思路是删除x的前驱节点,并且将前驱节点的value值以及key值复制到x节点,将其覆盖。
为什么这样做,根据二叉搜索树的特点,某一个节点的前驱节点一定是它的右子树中最右边的节点,也就是右子树中最小的节点,这个节点的特点是:它最多有一个孩子节点,这样的话就可以使用上面的方法来对其进行删除操作了。
下面伪代码
void delete(Node * root,Node *x){ Node * y=NULL; if(x->left==NULL||x->right==NULL){ y=x; }else{ y=SUCCESOR(root,x); } //case 1 if(y->right==NULL&&y->left==NULL){ if(y->f==NIL){ treenode=NIL; } else{ if(y->father->left==y) y->father->left=NIL; else y->father->right=NIL; } } //case 2 if(y->right==null||y->left==NULL){ Node * child=NULL; if(y->right!=NIL) child=y->right; else{ child=y->left; } if(y==treenode){ treenode=child; child->f=NIL; }else{ child->f=y->father if(y->father->left==y){ y->father->left=child;} else{ y->father->right=child;} } } if(y!=x){ x.key=y.key; x.value=y.value; } //case 3 //there is no case 3 here, because it has been transformed delete y; }
posted on 2015-05-29 15:07 lightblueme 阅读(262) 评论(0) 编辑 收藏 举报