[Happy DSA] 将已排序的元素序列快速的插入到stl set中

已知一个从小到大已排序的元素序列,如何插入到stl set中最快。

1. stl set内部结构

我们知道stl set内部是用红黑树来实现的。红黑树是一种平衡二叉查找树,它有以下4个用来平衡的条件:

  • 每个节点要么是红色,要么是黑色
  • 根节点为黑色
  • 红节点的子节点一定是黑色
  • 任一个节点至页节点的任何路径上,黑节点的个数相等

所以当插入一个新的非根节点时,它一定要是红色,是为了不破坏条件4。所以最难满足的就是条件3。为了满足这个条件3,没插入一个红色节点后,都需要进行树的平衡调整。
回到题目中,要是插入速度最快,就得尽量减少RB树的平衡调整次数或花销。

RB树条件有两种条件方式,一种就是简单的将父辈节点的颜色进行调整,譬如红色变成黑色,黑色变成红色;另一种就是旋转并调整颜色,有时不止一次旋转。简单的颜色调整是必须的,减少不了。因为默认都是以红色来插入一个新节点,不可能所有的节点都是红色。因此,我们要做的就是尽量减少旋转的次数,甚至根本不要让它进行旋转来调整。

2. 按照什么顺序插入

假如排序的数组元素序列是: 0, 1, 2, 3, 4, 5, 6, 7, 8。共9个元素。
如果了解二叉查找树的特性的话,一个比较稳定的树,或者说是一个比较方便以后查找元素的二叉树,应该满足左右子树元素个数尽量对称的布局。RB树既然是一个平衡二叉查找树,那么它也应该满足这样的优势,可以让之后的查找比较迅速(不然也不会采用RB树作为set的内部实现了)。我们期望的树的布局应该是下面这样的:


既然不要RB树内部进行旋转调整,那么树的根节点应该最先插入,那就是节点4。节点4是排序序列的最中间的元素。为了保持树的左右平衡,我们能想到的就是在左子树中插入一个树之后,右子树也对应的插入一个节点。注意要保证在每插入一个节点后,只允许更改相关父辈节点的颜色,不能进行旋转调整。同时我们要注意到,左右子树插入的情况类似于原树的插入情况,因为节点4被选出首先插入RB树,会将排序序列分成2个相等的左右排序子序列。因此我们想当然的期待着节点4左边的全部节点插入到左子树中,右边的全部节点插入到右子树中。依照这样的思路,选出4插入之后,选出左边子序列的中值,然后选出右边子序列的中值,依次插入。分别是第n/2 => (n/4 => 3*4/n)。如下图所示:

这里容易有一个误区,就是递归的插入中间元素,譬如插入4之后,再递归插入左半部分的中间元素,之后是右半部分的中间。其实这样是不对的。插入4之后,插入红色节点2,之后是红色节点0/1,这时就得进行右旋转调整了,因为0/1的父节点是红色节点。所以我们必须对称的插入左右中值元素,以保持RB树的平衡。

3. 算法验证及实现

上面插入的顺序到底是不是我们的一厢情愿呢?我们来验证一下,还是以前面9个数字序列:0, 1, 2, 3, 4, 5, 6, 7, 8。根据我们前面推导的插入序列,它们应该按照这样的方式插入:4, 2, 6, 1, 3, 5, 7, 0, 8
a) 插入节点4,标记为黑,因为它是根节点。


b) 然后是节点2,和节点6。插入红色节点2后,它的父节点是黑色节点4,因此不需要任何颜色调整。插入节点6时,父节点4是黑色,跟节点2一样,不需要调整颜色。

c) 接着是节点1
红色节点1作为节点2的左子节点插入,由于父节点2是红色,而且叔叔节点也是红色。根据RB树的调整规则,我们只需要将父辈节点2和节点6反转为黑色,将祖父节点4反转为红色。但是最后又得将根节点4标记为黑色(满足规则2)。

d) 节点3,5,7
有了前面1的插入,导致现在节点2和节点6都是黑色,节点3,5,7将被直接插入,不做任何调整。

e) 节点0,8的插入
红色节点0作为节点1的左子节点插入。由于节点1和节点3都为红色,类似于当时节点1的插入,将节点1和节点3反转为黑色,将节点2反转为红色。节点8类似于节点1。

高亮标记的那个树就是RB树内部的结构。

从节点4到节点8的插入,期间只有颜色调整,没有任何旋转调整。因此满足我们之前的需求。

下面就是序列插入时,RB树内部结构变化图:



一个基于BFS的Python代码的实现(借用队列):

#insert all elements from l (type is list) to s (type is set)
def insert_set(l,s):
   sorted_l = sorted(l)
   import Queue
   q = Queue.deque()
   begin, end = 0, len(l)
   q.append((begin,end))
   while len(q):
      (begin, end) = q.popleft()
      if begin >= end:
          continue
      mid = (begin + end) /2
      print mid
      s.add(l[mid])
      q.append((begin,mid))
      q.append((mid+1,end))

if __name__ == '__main__':
    l = [0,1,2,3,4,5,6,7,8]
    s = set([])
    insert_set(l,s)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值