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

来源:互联网 发布:网络共享 win10 编辑:程序博客网 时间:2024/05/29 03:07

已知一个从小到大已排序的元素序列,如何插入到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)



原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 娃娃写作业慢怎么办 写作业眼睛疼怎么办 发生火灾怎么办大班教案 大班健康发生火灾怎么办 学生上课不提问怎么办 入户通知单丢了怎么办 打架后还来找事该怎么办 着火了怎么办教案视频 电脑一小半黑屏怎么办 绝地求生闪退怎么办 win7没浏览器了怎么办 浏览器被删除了怎么办 把快捷方式删了怎么办 ie文件找不到了怎么办 ie文件不存在了怎么办 大学素质分不够怎么办 素拓学分不够怎么办 大学毕业之前素拓分拿不满怎么办 武汉幼儿医保卡怎么办 养老院护工欺老人怎么办 皮肤毛孔粗大有痘印痘坑怎么办 额头上毛孔大怎么办 额头皮肤毛孔大怎么办 脸部粗糙毛孔大怎么办 脸上有痘印毛孔粗大怎么办 毛孔粗大痘印怎么办 教官12123一直加载怎么办 教官嗓子哑了怎么办 喜欢上考场教官怎么办 跟教官打起来怎么办 车险贴丢了怎么办 大学不想军训该怎么办 职高军训不想去怎么办 上大学不想军训怎么办 收费站忘记带钱怎么办 孕妇咳得厉害怎么办 怀孕九个月咳嗽怎么办 门冬氨酸高怎么办 代理保证金不退怎么办 电脑游戏太大下载慢怎么办 四川百裕制药怎么办