高级搜索树之红黑树
也就是增量更新的思想。红色的线为共享的结点,蓝色的为增量更新的结点。看上面图的意思,增量更新似乎只在父结点(包括左右孩子)变动时才会使用。
AVL树的插入操作可以做到O(1),然而删除却要O(logn),而红黑树所有的动态操作都保持在O(1)。
例子:
提升变换
就是将红色结点放到和黑色父结点一样的高度。
这看起来很像B树。事实上就是这样的,而且原来的叶子结点深度参差不齐,但是提升变换后全都一样了,这是由所有外部结点到根节点经过黑色结点个数相同决定的。
实现
插入
可以借助B树理解红黑树。
这是zigzig和zagzig的情况。那么此时考虑对应的B树,合法的红黑树的中心关键码应该为黑色,那么只需重新染色即可。x的孩子都是外部结点,为黑色,黑色高度为1,而p的右孩子(左孩子的高度必然也为1),因为原来p的左(右孩子),也就是插入的地方原来为外部结点,高度为1,为了保持性质3,那么另一个孩子的高度必然也为1(只有外部结点为黑色),否则不满足性质3,g的另一个孩子同理分析。
这样3+4重构的中序遍历保持性无需证明了。这种拓扑结构的改变为O(1)。
这种情况会导致(2,4)B树的上溢。
这种分裂操作不会导致红黑树的结构发生变化。不过父结点可能也出现双红。最终的拓扑结构该变量依然为O(1)。
这个过程只需要一次重构,拓扑结构改变量为O(1)。重构是持久性结构关注的内容。AVL树的插入操作也能做到拓扑改变为O(1),但是删除操作改变的拓扑结构为O(ogn)。那么红黑树的删除呢?
删除
简单情况
如果x为被删除的结点,r为替代的结点,假设一黑一红,则只需将染色为黑色,因为这种操作相当于去掉一个红边(虚线),虚线对于高度(只数黑节点是没有影响的)。上面两种情况都不会发生B树结点的下溢。
双黑缺陷
这样势必不满足性质4,同时这等效于B树超级节点的下溢。因为x是有一个关键码的超级结点,删除了就会下溢了。(提升操作是遇到红孩子节点才上提为超级结点,如果孩子都是黑色,就自己当作一个超级结点)。当然这种下溢还有一个条件就是s为黑色。
这种3+4重构等于是将一个红结点边为黑结点,这样弥补了删除一个黑节点失去的一个高度。
这种拓扑结构的变化是最大的。但他不会导致上层的下溢。
这种操作没有改变拓扑结构,不过下溢会上传。不过最后的改变也不超过O(1)。
也就是红黑树的动态操作都为O(1),这对于增量式的存储无疑是福音。
词典
电话其实是可以带字母的。这种电话号码会非常好记,而且对应到这家公司。当然其实你最后打出去的还是数字,只不过有一些字母辅助你记忆而已。这就是一种映射。这种访问方式叫做循值访问。对应得数据结构叫做散列。
冲突
散列函数
除余法
对M取余为何相当于对和M-1做与运算呢?想一下求2的余数,其实就是原数和1与,这个道理是一样的。这样的话,冲突的概率确实挺大的,因为只需要后多少位二进制相同即可。如果取一个素数,则分布得就会较为均匀。
假设步长为S,区间长度为M,如果S步进的点可以覆盖整个M,则M中的分布大概为均匀的。由数论的知识,S和M互质,而S可以变化,于是M取素数更好。这和蝉相似,蝉的生命周期都是13,17这样的素数,就是尽量避免在生命周期里各种S的天敌遇到得太多。
MAD法
b解决了不动点的问题,a解决了零阶均匀。
这种方法其实也叫做线性同余发生器(马特塞特旋转法),是一种产生伪随机数的方法。
平方取中
为何取居中的数位:
如图是将平方操作分解为乘法(左移)求和,可以看到取中间的位数可以保证原数尽可能多的数去影响最后的结果。
折叠法
散列函数越没有规律越好。
伪随机
我们将key作为伪随机数发生器的seed即可。
可以把难题推给伪随机数发生器,但是,(伪)随机数发生器的实现,因具体平台不同厉史版本而异。
创建的散列表可移植性差故需慎用此法。
多项式法
这种算法主要针对于字符串的key。首先选取一种编码,比如UTF-8,ASCII或者ANSI等。UTF-8适用的范围更广一些。将每一个字符转化为对应的编码的十进制。然后送进一个多项式函数。这个多项式函数可以通过秦九韶算法将复杂度降为O(n)。
不过乘法的运算时间是加减的4倍,我们尽量避免乘法。于是有了下面这种方式,这里并没有用乘法,而是用按位或。一般h的位数为32位,这里将其左移5位的结果和右移27位的结果做或运算。
这样的效果是
27 | 5
5 | 27
由于是或操作,左边22位保持不变,而右边的22位也不变,中间的10位可能会变化,这和平方取中的感觉类似吧。
VORLDMORT
关于哈利波特的轶事。将字符结果简单相加。
这…
解决冲突
链地址法(独立链)
所以直接用链地址法,将槽位用列表实现。
这种操作需要动态申请,动态申请耗时大概是一般操作的100倍,而且由于链表存储的物理不连续,可能这些结点不在一个页面,就需要多次读取,这也是不利。
开放定址法
链地址法叫做封闭定址。
开放定址的思想是每个bucket都可能作为一个key的存储位置,不过是有一个优先级的,也就是一个存储(探查)的序列,如果序列的第一个元素位置为空,直接存,依次类推。
那么这个查找链如果构造呢?
一次探查(线性试探)
这样存储的话,物理空间连续。但是可能导致二次聚集,也就是后续的冲突。
假设这个哈希函数是模7的余数。
7插入的时候进行了5次试探才确定它的位置。
这还算少的。如果先插入的是7的话。0,1,2,3就都发生了冲突。
对于做标记的这个位置,如果是查找,则直接跳过,如果是插入,它按照空桶处理,一个位置必然对应多条查找链。
二次试探
但是这样会导致物理地址的不连续。可能会多次读取页面。
如果页面大小为1kB,每个位置为4个字节,那么如果连续发生16次冲突,就需要读取下一个页面了。
不连续的存储还有一个问题。只要有空桶,就一定能被找出来吗?这个需要进一步分析。
这个问题其实也就是在0-M间完全平方数的个数。
完全平方数为,如果n为偶数,则完全平方数为4的倍数。
n为奇数,则完全平方数为8的倍数加1。
如果M为素数,装填因子小于0.5,则空桶一定会被找出。
证明:
这表明(b-a)或者(b+a)为M的因子。矛盾。这说明前M/2项必然不可能冲突。
它们必然互异。后面则无法保证。这说的是应该是假设hash(key)=0,假设探查到a的时候才存下了,中间假设存了若干个hash(
k
e
y
i
key_i
keyi)=0,假设插入hash(
k
e
y
i
key_i
keyi)=0,该探查b了。
b
2
b^2
b2完全可以大于M,它轮回到偏前面的位置,这个说的是和前面的a不可能是同一个位置。那就不会冲突。当然这个起点不一定为0,也就是hash(key)不一定为0,只要从起点开始的M/2就是不可能冲突的。也就是一定能找到[M/2]个位置的空桶。其它[M/2]个位置可能访问不到,当然这个访问不到是对于某一个hash(key)而言的,开放定址法的思想就是对于一个key,它可能存在哈希表中的任何地方,然而现在并不是。因为M为素数,所以M/2的向上取整一定为大于M/2的数而且其实λ不可能等于0.5,只能小于0.5。此时,必然还存在空桶在key的探查序列中可以存放。因为哈希表必有一半的位置可以被访问到,而现在填满的才不到0.5,不管怎么填,总有空桶可以被试探到(这也可以同抽屉原理理解)。
这样的双向平方查找链必然可以覆盖所有的桶?其实也不一定,比如M=5,M=13。它也没有覆盖全部的桶。
但是7和11就可以。其实是4k+3的才可以。
证明:
这个双平方定理不难得到。首先p是素数,自然不可能为偶数,所以这两个整数必然一奇一偶。那么
(
2
m
)
2
+
(
2
n
+
1
)
2
=
4
m
2
+
4
n
4
+
4
n
+
1
(2m)^2+(2n+1)^2=4m^2+4n^4+4n+1
(2m)2+(2n+1)2=4m2+4n4+4n+1
再根据平方和相加可以转化为相乘的定理。
然后是整数质因子分解的唯一性,质数只能为
4
k
+
1
或
4
k
+
3
4k+1或4k+3
4k+1或4k+3。
首先4k+1可以分解为平方和,那么只有4k+3的幂次为偶数才可能分解为整数平方和相加了。
然后是反证法,假如正的探查a和负的探查b冲突,则得到 a 2 + b 2 a^2+b^2 a2+b2为M的倍数,则M为其因子,根据上面的结论, M 2 M^2 M2也是它的因子,则有
然而
这个限制是因为保证正向和负向的不冲突性同时保证试探总次数小于M。
即使a,b都取得上界,上述不等式也不可能成立。
桶排序,计数排序
M为桶的个数。
那么这样的算法的复杂度就是O(n)。
这个计数只需要O(n)遍历一次即可,而累计值只需要线性扫描count向量即可,为O(M)。
优先级队列
急诊科的医生肯定也是按照病的轻重程度考虑先治谁,而如果程度一样,则按照先来后到的顺序。
在计算机中也经常使用,进程会有优先级别。
虽然在末尾插入的效率会很高,但是删除和找最大优先级的函数就不行了。那么我们看看有序向量:
但是插入的操作也不行了。
那么列表呢?
那么该用什么呢?BBST?
用BBST就是杀鸡用牛刀。
完全二叉堆
这种堆其实也就是大顶堆。
子节点不大于父结点的值。注意上图的数字是索引而不是值。
插入
e如果互换后依然大于父结点,那么一直交换直到根节点。
例子:
这其实也是堆排序的过程。插入对应的是上滤调整。
最坏情况下上滤操作为logn次,而每次上滤操作都有一次比较,和两个元素的交换。两个元素的交换总需要3次,无论是引入temp还是异或操作。
而我们其实可以先不交换,而是奖e备份,如果e大于父结点,父结点下移,最后再将e归为,这样可以实现ogn+2。
而比较操作我感觉可以先和r比较。可以减少比较次数。
现在分析下上升的高度的期望。
假设插入位置的父结点链长为n,那么插入的元素为n+1种可能。
期望为
1
n
+
1
Σ
n
=
(
n
+
1
)
n
2
(
n
+
1
)
=
n
2
\frac{1}{n+1}\Sigma{n}=\frac{(n+1)n}{2(n+1)}=\frac{n}{2}
n+11Σn=2(n+1)(n+1)n=2n。
不过据说应该是O(1)。
删除
下滤操作:
这也和堆排序一样。
这个ProperParent求的是孩子结点的最大值,如果现在的e小于最大的,那么他和最大的交换,或者说为父结点的最小值。实际上由于e为树的叶子,第一次交换绝对是需要的。也就是至少一次交换。循环推出的条件是要么现在的i已经大于最大的孩子结点(之所以取大于是为了同等优先的先来后到),所以我比较疑惑为何是不等于而不是大于。e成为叶子结点也是一个退出循环的条件。
每次循环的比较操作为2次而非上滤的一次了。交换也可以做到logn+2次。
建堆
自上而下
数据的插入方向是自上而下,第一个元素为根节点插入,后面的都要做上滤。
这个偏序指的是只是父结点大于等于孩子结点而已,而不是像BST那样。
自下而上
而自下而上则相反,自下而上不难想到需要合并两个完全二叉树。合并其实和删除p的下滤过程类似。n为向量的长度,i为下滤元素的位置。
其中LastInternal求的是最后一个出度为2或者1的结点的位置,它是
⌊
n
2
⌋
−
1
\lfloor \frac{n}{2}\rfloor-1
⌊2n⌋−1。
这个是如何计算的呢?由二叉树的基本关系式:
n
1
+
2
n
2
=
n
−
1
n_1+2n_2=n-1
n1+2n2=n−1
而
n
1
n_1
n1得值只能取0或者1。那么
n
2
=
n
−
1
−
n
1
2
n_2=\frac{n-1-n_1}{2}
n2=2n−1−n1
如果
n
1
=
1
,
n
必
为
偶
数
n_1=1,n必为偶数
n1=1,n必为偶数,最后一个内部节点得秩为
n
2
−
1
\frac{n}{2}-1
2n−1,注意秩从0开始。
反之,n为奇数,最后一个内部结点为
n
−
1
2
\frac{n-1}{2}
2n−1,它的秩为
n
−
1
2
−
1
\frac{n-1}{2}-1
2n−1−1
都符合
⌊
n
2
⌋
−
1
\lfloor \frac{n}{2}\rfloor-1
⌊2n⌋−1
这种下滤的算法相当于对少数富人征收更多的税(更大的高度而非深度)。
高度求和的话,最后的内部结点的高度必然为1。然后倒数第二个可能为1或者2,这个高度的变化会很慢,因为越下面结点越多,所以高度小的结点多,而深度则相反。根节点的高度为logn,为方便分析,我们用满二叉树分析。
令
m
=
l
o
g
(
n
+
1
)
−
1
令m=log(n+1)-1
令m=log(n+1)−1
则
m
+
2
(
m
−
1
)
+
4
(
m
−
2
)
+
.
.
.
n
+
1
2
m+2(m-1)+4(m-2)+...\frac{n+1}{2}
m+2(m−1)+4(m−2)+...2n+1
这是个等比等差混搭的数列,可以用错位相减求和,或者看作级数求和。
∑
n
=
0
m
−
1
2
n
(
m
−
n
)
\sum \nolimits _{n=0}^{m-1}2^n(m-n)
∑n=0m−12n(m−n)的和为S。
2
S
=
2
m
+
4
(
m
−
1
)
+
.
.
.
(
n
+
1
)
2S=2m+4(m-1)+...(n+1)
2S=2m+4(m−1)+...(n+1)
2
S
−
S
=
−
m
+
2
+
4
+
.
.
.
2
m
−
2
+
n
+
1
=
O
(
2
l
o
g
(
n
+
1
)
+
n
)
=
O
(
n
)
2S-S=-m+2+4+...2^{m-2}+n+1=O(2^{log(n+1)}+n)=O(n)
2S−S=−m+2+4+...2m−2+n+1=O(2log(n+1)+n)=O(n)
结束。
堆排序
堆排序就是自下而上建堆,然后删除堆顶最大的(将他放入已排序部分)。
复杂度为O(nlogn)。而且堆排序是就地算法,空间复杂度为O(1),这个空间也是交换需要存放副本的空间。
左式堆
堆的合并可以用自下而上的建堆,但是这样没有利用原来两个堆的偏序性。
堆序性才是堆的本质特性。
实例:
左子树的高度和规模未必大于右子树。
完全二叉树的右链长度为O(logn)。
合并a和b的步骤为合并
a
R
a_R
aR和b,然后再比较
a
L
a_L
aL和合并后的
a
R
a_R
aR的NPL,如果有必要,则进行交换,使得这个堆为左式堆。左式堆的实现底层已经不宜使用向量了。
实例:
这个是沿着右链进行的,因此复杂度为O(logn)。
目前优先队列的建堆需要O(n),每次下滤需要O(logn),插入也是O(logn)。
插入与删除