代码改变世界

STL源码剖析之关联式容器基础知识

2011-06-12 10:11  Aga.J  阅读(359)  评论(0编辑  收藏  举报

54 标准的STL关联式容器有 set (multiset)和 map(multimap),他们底层的机制都是RB-tree即红黑树来实现。还有其他 hash_table, hash_set,hash_map,hash_multiset,hash_multimap(他们的底层都是以hash_table来实现)

 

RB-tree

   Set (RB_SET)

   Map (RB_MAP)

   Multiset (RB_MULTISET)

   Multimap (RB_MULTIMAP)

Hashtable

   Hash_set

   Hash_map

   Hash_multiset

   Hash_mutimap

 clip_image002

  关联式容器,概念上类似“关联式数据库”每个元素都有一个key和value

  当元素插入到关联式容器中时,容器内部结构RB-tree(平衡二叉树,有较好的搜索效率,还有其他类型如AVL,RB,AA)或者hash-table就会按照key的大小,用某种规则将元素放在适当的位置

 

55 平衡二叉树

   不同的平衡条件,造就出不同的效率表现,以及不同的实现复杂度AVL / RB  / AA都是一种平衡二叉树,因为维护平衡,所以插入和删除节点的平均时间长了,但是可以避免不平衡的情况,所以元素的搜寻访问时间短了!

  最直观的平衡条件是整棵树的深度为logN,并要求每个节点的左右子树有相同的高度(递归定义的结果就是该树的叶子节点都在同一层上!)

  而AVL- Adelson-Velskii-Landis tree(AVL tree树则允许任何节点的左右子树的高度相差1,(递归定义的结果就是该树的叶子节点可以在最后一层和倒数第二层)

 

56 AVL树

一旦插入节点后,AVL树不平衡,只要调整“插入点到根节点”的那条路径上,平衡状态被破坏的所有节点中 【最深】的那个(怎么找到这个最深的?只要逐步向上溯,得到当前节点的父节点的左右子树的深度相差大于一),就可以使得整棵树重新获得到平衡 clip_image004

那么有以下四种情况

1 插入点位于X 的左子节点左子树 ----左左 (外侧)              //不管是在左子树的什么位置上插入

2 插入点位于X的左子节点右子树 -- 左右 (内侧)

3 插入点位于X的右子节点左子树 右左 (内侧)

4 插入点位于X的右子节点的右子树 右右 (外侧)

也就是说正确的找到X节点就显得十分重要了!

 clip_image006

  在外侧插入—也就是在 完成外侧插入后,某个顶点成为第一个违反平衡条件,是下面这种状态(违反点为k2)

也就是情况【1】【4】

1 插入点位于X 的左子节点的左子树 ----左左 (外侧)

4 插入点位于X的右子节点的右子树 (内侧)

clip_image008

  插入11后,使得A子树(k1节点的左子树)成长了一层,但是这样并不会让k1树不平衡,但是A子树的深度却比C子树的深度2,也就使得k2左子树的高度为3,k2的右子树的高度为1,破坏了平衡。而且这种情况下,B子树不可能和A子树同层,不然的话早就不平横了,同时B子树也不可能和C子树同层,不然的话第一个违反平衡条件的就是k1而不是k2了。

  我们希望A子树向上提,而C子树下降一层,可以从图里这样想象,只需要把K1向上提,而K2自然下滑,并且把B子树挂到K2的左侧,就完成了这个过程(因为本身AVL是一颗平衡二叉树,子树有一定的大小关系,所以这样操作可以得到正确的结果)右右插入的时候同理!

clip_image010

在内侧插入 – 也就是在情况【2】,【3】

2 插入点位于X的左子节点的右子树 -- 左右 (内侧)

3 插入点位于X的右子节点的左子树 (外侧)

clip_image012

这次我们没办法对k1和k3只进行一次旋转,使得k1为根节点来完成平衡。我们可以考虑用k2节点来作为平衡树的根,这就需要两次旋转

clip_image014

对照着上述的旋转方法,给出基本的旋转操作(暂时不考虑其他节点为空的情况)

Void singleRotation( Node* n,bool left)                                           //n节点表示当前节点违反了平衡条件

{

  Node *newHead;

  If(left==TRUE) //新插入的节点在n的左子树

左左,进行右上提拉节点,注意只适合左子节点的左子树,如果是左子节点的右子树,则需//要两次旋转,意思就是只适合外侧

  {

    newHead = n->left;          //在这里指k2

    if(newHead->right!=null)

         n->left=newHead->right;

   newHead->right=n;

   n=newHead;

}

Else                        //右右,进行左上提拉节点

{

  newHead = n->right;

  if(newHead->left!=null)

     n->right= newHead->left;

  newHead->left = n;

  n= newHead;

}

}

Void doubleRotation(Node* n, bool left)

{

If(left == TRUE)                                               //新插入的节点在n的左子树

//左右,注意只适合左子节点的右子树,如果是左子节点的右子树,值需要一次旋转,内侧旋转

{

singleRotation(n->left,false);   //第一次旋转,对左子节点树进行一次左旋,如下图所示

singleRotation(n,true);              //第二次旋转,然后对该节点进行一次右旋

}

Else

{

singleRotation(n->right,true);

singleRotation(n,false);

}

}

clip_image014