20220428
教材:算法导论、具体数学、数据结构与算法分析。
递归:减而治之 or 分而治之。
迭代:
ADT = “说明书”。
Pavlov said his dogs are male forever.
20220429:
插值查找 = 查字典
Pavlov is more niubi than Freud.
20220502:
树 = List<List> = (List)^2
20220503:
树 = 无环有向连通图
二叉树 = 有根有序树
无向图 = 双向边有向图
网络 = 带权有向图
20220504:
Graph(非线性)→ Tree(半线性)→ Sepuence(线性)
Graph的遍历过程 = 搜索(search)
图广度优先搜索(Breadth-First Search, BFS)(Queue) = 树层次遍历的推广
图深度优先搜索(Depth-First Search, DFS)(Stack)
优先级搜索(PFS)
MST(minimum spanning tree):shortest bridge 有时不唯一,所以MST有时也不唯一。
20220507:
BST(二叉搜索树):局部有序性,全局单调性。
BBST(渐近和适度平衡意义下):平衡二叉搜索树(包括AVL、R/B等,参考《算导CLRS》第13章章末注记)。
CBT(理想平衡意义下):完全二叉树
CBT ∈ BBST ∈ BST
BST = Vector + List
BST:node~entry~key(赛车手~赛车~车牌号)
词条模板类Entry = <key, value> pair
20220508:
AVL:平衡因子,树高度。
zig = 顺时针旋转:左分支更深,左下向右上。此时的再平衡旋转叫zig。
zag = 逆时针旋转:右分支更深,右下向左上。此时的再平衡旋转叫zag。
zigzig:(顺时针旋转两次)(从叶向根)左下向右上、再向右上。
zagzag:(逆时针旋转两次)(从叶向根)右下向左上、再向左上。
zigzag:(先顺时针旋转、再逆时针旋转)(从叶向根)左下向右上、再向左上。
zagzig:(先逆时针旋转、再顺时针旋转)(从叶向根)右下向左上、再向右上。
AVL(3+4)-重构:(最低失衡节点至少是动态操作节点的上三代,把爷父孙三代中序遍历,得到)3个节点与4个子树。
AVL(3+4)-重构 = 简化zig、zag等旋转操作。
伸展树(Splay-Tree):没有平衡因子之类指标,不需额外封装。
R/B:动态重平衡后,全树拓扑结构变化量稳定在O(1)。
(AVL全树拓扑结构变化量,insert=O(1),remove=O(logn))。
高级数据结构和算法,对【全树拓扑结构变化量】有严格要求。
Splay-Tree(宽松平衡意义下):伸展树(∈ BST)。每访问一个节点,便将该节点旋转为根节点。
Splay-Tree的zig-zig和zag-zag,与AVL重平衡的zig-zig和zag-zag,效果相同;
AVL重平衡的zig-zag或zag-zig:从孙到爷,父先旋转、爷再旋转(自下而上)。
Splay-Tree双层伸展的zig-zag或zag-zig:从爷到孙,爷先旋转、父再旋转(自上而下)(Tarjan,1985)。可缩小全树高度,使最坏情况的时间复杂度为O(logn)。
B-树:实现数据的高效I/O。不属于BST,但逻辑上等效于BST。
磁盘访问速度 ms级;内存访问速度 ns级,至少相距100000倍。
I:低层存储器把数据读入(输入)高层。O:高层存储器把数据写到(输出)低层。
I/O:输入/输出。
20220509:
B-Tree:(Bayer)
(叶节点深度相同)理想平衡,比BBST更宽更矮(矮胖);
动态调整拓扑结构;
B-Tree = 多路平衡搜索树 = (多代合并的)BBST;
(优点)多代合并而成的超级节点,可批量访问外存.
m路B-Tree = m阶B-Tree = ([m/2],m)-Tree
m = 超级节点的最大分支数。
n = 超级节点的最大合并节点数(该超级节点的关键码key的最多个数)。
m = n+1.
[ ] = 向上取整。
(2,4)-Tree 与 RB-Tree 有关。
B-Tree的查找 = 内存中的Vector顺序查找 与 I/O操作 间隔进行。每次只读入必需节点,尽可能减少I/O操作。
外部节点:将存放于不同存储级别上的B-Tree们串接起来。
为让每次I/O操作的延迟时长(内存访问的100000倍)更划算,每个超级节点的大小应尽量与一次I/O交换页面的大小相匹配。一次I/O操作的交换页面大小通常为几Kb,每个关键码key大小通常为4Byte,因此 1Kb 对应 200-300个key,每个超级节点大致有几百个key(几百个合并节点)。
B-Tree的高度h = 最低层虚拟的外部接口的高度h。
B-Tree的h 比BBST 多算一层。
每查找一次,都只有两种结果:成功找到 or 查找失败。
若B-Tree内总共有N个key,则对应N次成功可能,则相应有N+1种失败可能。
N个内部节点,N+1个外部节点。
N种成功可能,N+1种失败可能。
B-Tree高度h增加的唯一情况:Insert新节点(新关键码key)时,发生超级节点的上溢(overflow),该超级节点拆牌后又overflow……一路overflow到根超级节点,根超级节点也overflow、再拆牌形成新的根节点。此时,新的根节点只有一个关键码key、只有2个分支。
insert - overflow - 拆牌分裂
remove - underflow - 合并
B-Tree高度h减少的唯一情况:remove某关键码key1时,先找到key1的直接后继(先取key1的右分支超级节点,取该超级节点最左端的关键码key2、即该超级节点向量的第一个元素;再取key2的左分支,取该左分支超级节点最左端的关键码key3……重复上述找左分支的操作,直至 叶超级节点,该叶超级节点的最左端关键码即key1的直接后继),交换key1及其直接后继。此时key1在某叶超级节点内,remove key1之后,该叶超级节点可能underflow(n < [m/2]-1,此处[ ]为向上取整),此时该叶超级节点的节点数 n = [m/2]-2。在该叶超级节点处,左顾右盼,若其左右兄弟超级节点不存在、或其左右兄弟超级节点的节点数=[m/2]-1,则无法用“旋转”操作来满足B-Tree超级节点内的节点数n的取值范围([m/2]-1 <= n <= m-1),此时改用“合并”操作。“合并”操作会从key1所在叶超级节点的parent超级节点中取走一个key,因此该parent超级节点也可能underflow,重复上述“无法旋转、改用合并”的过程,直至 根超级节点。碰巧,根超级节点只有一个关键码key、只有2个分支,“合并”后,该 根超级节点没有key、只有一个分支,以这一分支为新的根超级节点,此时B-Tree高度减少为h-1.
B-Tree 水平方向上,是在内存RAM中搜索,速度快、耗时少;
B-Tree 垂直方向上,是在磁盘DISK中进行I/O操作,速度慢、耗时多(单次I/O耗时是RAM的100000倍)。
为平衡RAM与DISK之间的整体耗时,应尽量多在水平方向上搜索,尽量少在垂直方向上I/O,所以B-Tree“矮胖”。
20220510:
R&B-Tree:一种BBST,其中任何一次动态操作(insert、remove)引发的树形拓扑结构变化 不超过 O(1),即任何一次insert或remove都只引发常数次“旋转”(zig+zag双旋(即(3+4)-重构),或zig/zag单旋)。
R&B-Tree == half-BBST ∈ BBST
各红节点均提升一层(类似二代合并)之后的R&B-Tree == (2,4)-Tree == 某一种B-Tree
(1)为保证是真二叉树,提前为R&B-Tree的每个叶节点增加1或2个虚拟的黑外部节点;
(2)黑帽(根节点为黑)黑鞋(外部节点为黑);
(3)红节点上下 只领接 黑节点——保证树的高度;
(4)任一黑外部节点 到 根节点 的唯一路径上,黑节点数目相等——因此,路径上的各红节点均提升一层(类似二代合并)后,任一底层黑外部节点的高度 H 均相等,即叶节点等高平齐——保证树的左右平衡性。
R&B-Tree的高度h 代码实现时一般是指 黑高度H。
h <= 2H,因为红节点上下只领接黑节点,任一叶根路径上的红节点均不多于黑节点,所以任一叶根路径上 红+黑 <= 2*黑,即h <= 2H。
R&B-Tree ->(lifting)-> (2,4)-B-Tree
R&B-Tree insert操作的RedRed缺陷:
爷孙三代red lifting(类似二代合并)之后,依uncle节点的黑或红而有2种情况——
(1)若uncle节点为黑,(3+4)-重构(或zig、zag)之后,重染色O(1)次,局部拓扑结构改变O(1)次;
(2)若uncle节点为红,此时red lifting后、对应的(2,4)-B-Tree发生overflow,并 可能 向根节点传递发生多次overflow(某层发生一次overflow,该层需重染色一次;最坏情况(有logn层)发生logn次overflow),则重染色至多O(logn)次,局部拓扑结构改变0次。
R&B-Tree单次insert,至多只发生一次(3+4)-重构,即局部拓扑结构只改变一次,这有利于persistent structures(持久化结构,便于记忆 树的时间轴版本,详见计算几何)。
R&B-Tree remove操作的BlackBlack缺陷:
某路 上下两代节点均为黑,remove之后,对应的(2,4)-B-Tree发生underflow。
【BB-1】 —— R&B-Tree ->(lifting)-> (2,4)-B-Tree,remove后为下溢节点,(3+4)-重构一次(或zig+zag双旋一次),以sibling节点为新的爷节点,重染色1或2次。
【BB-2R】 —— R&B-Tree ->(lifting)-> (2,4)-B-Tree,remove后为下溢节点,无法zig/zag旋转,改用“合并”,重染色2次,局部拓扑结构改变0次。
【BB-2B】(局部全黑)—— R&B-Tree ->(lifting)-> (2,4)-B-Tree,remove后为下溢节点,无法zig/zag旋转,改用“合并”,重染色1次(sibling节点由黑染红),合并后的(2,4)-B-Tree发生上层的underflow、并 可能 向根节点传递发生O(logn)次underflow。因此,对应的R&B-Tree至多重染色O(logn)次,局部拓扑结构改变0次。
【BB-3】 —— R&B-Tree ->(lifting)-> (2,4)-B-Tree,remove后为下溢节点,zig/zag单旋一次,重染色2次(sibling节点由红染黑,parent节点由黑染红),(2,4)-B-Tree -> R&B-Tree,局部拓扑结构改变一次。此时(parent节点已染红),已转化为BB-1或BB-2R(若转化为BB-2R,则局部拓扑结构还需改变一次;因此,BB-3总计改变局部拓扑结构1或2次)。
R&B-Tree的remove操作,至多只需O(logn)次recoloring、以及1或2次局部拓扑结构改变(在persistent structures方面至关重要),这一特点优于AVL的remove操作。
20220511
散列(Hash):
call-by-rank(下标):Vector
call-by-position(地址):List
call-by-key(关键码):BST
call-by-value(访问对象本身):Hash
在巨大数据空间(词条空间)里,只存储和组织非常少的数据(hash table地址空间),例如电话簿、词典等。若直接用数组(array),则空间效率极低,可将array改进为Hash。
array中的每个单元 == Hash中的桶单元(bucket)
array == 桶数组(bucket array) == 散列表(hash table)
关键:适当选取Hash table的长度M,M大小最好同阶于(非常少的)真正存储和组织的数据量。
Hash table适当压缩 巨大数据空间(词条空间)。
定址 == 杂凑 == 散列 == Hashing:确定 目标词条 位置的过程,可用 散列函数(hash function)【hash():key -> &entry】。
Hashing:key -> hash(key) -> bucket/&entry -> entry -> 目标词条。
电话号码(key) -> hash(key)=key%M -> 机主信息(bucket):
时间复杂度 O(1),
空间复杂度 == 装填因子(load factor) == N/M 大大提高。
hash function 把 巨大词条空间 压缩为 非常小的hash table地址空间(空间大小为M),O(domain) >> O(image),难以实现one-to-one,极易出现“多对一”冲突。
蝉的哲学:蝉有不同变种,但每一变种的生命周期都是 素数。这样的进化,保证蝉与各种天敌(螳螂、黄雀等等)的生命周期的最大公因子都是 1 ;保证在蝉的生命周期内,各种天敌尽可能均匀出现(均匀性,遍历 蝉的全生命周期),不致于在某一年集中出现(Locality,clustering),把某一种蝉一次性吃完、使这一种蝉灭绝。
所以,hash table地址空间的大小M应为素数。
Geometric Computing 对高维数据进行降维压缩时,也可用Hash table,但却是 Locality-Sensitive Hashing:需要保证 邻近的key 依旧被mapping到 邻近的hash地址,这时无需高阶均匀性、甚至一般的零阶均匀性。
Hash(散列): 大空间 -> 小空间(压缩)。
cryptology(密码学): 大空间 <- 小空间。
Hash function:越随机、越没规律,就越好(为保证 关键码key的各位数字 对 散列地址 的影响力尽可能满足 均匀性)。
Hash:key -> hashcode -> (hash function()) -> bucket addr. -> entry