马上过年了,争取在年前把这本书看完,还有3章,嗯。
词典Dictionary和映射Map
逻辑上的词典和映射都是是由一组数据构成的集合,其中各元素都是由关键码和数据项合成的词条。
映射和词典的区别是:
映射要求不同词条的关键码互异,而词典允许多个词条拥有相同的关键码。
词典和映射统称为符号表。
跳转表 SkipList(如果说其它的数据结构都不记得了但其实对名字还是有些印象的,但这个概念是真的一点儿印象都没有了( ╯□╰ )。。。)
一种高效的词典,由Dictionary和LinkedList共同派生出来的。查询和维护操作在平均意义下是O(logn)。
其内部由 沿横向分层和沿纵向分层的多个链表{S0, S1, …… Sh}组成,h为其高度。
每一水平链表称为一层(level),S0为底层,Sh为顶层。通常S0包含词典中所有词条,Sh除头尾哨兵外不含任何实质的词条,所以跳转表从纵向上组成了一个塔结构。
四联表:(QuadList)
在横向和纵向上都可以定义前驱和后继的跳转表。(横向前驱pred,后继succ,纵向上邻above,下临blow)
跳转表的删除和插入的操作也都是在搜索的基础上进行的。
查找:
从顶层列表Sh的首节点p开始向右向下查找目标关键码k,直到出现更大的关键码或者溢出至该层列表尾,如果找到了,将p置为命中关键码的前驱,Sx为p所属的列表;否则p为所属塔的基座S0中不大于k的最大且最靠右的关键码, 所属列表置为空。
比如,想找到上图中的13,就要经历{ -∞,-∞,8,8,8,8},想找到89,就要经历{-∞,-∞, 8,8,34,34}
bool SkipSearch(SkipList<K, V> slist, K k)
{
SkipSearchNode<K, V> p = header;
while(true)
{
while((p.succ != null) && (p.key <= k))
{
p = p.succ;
}
p = p.pred; //while循环中多向后找了一个,所以这里回退一步
if((p.pred != null) && (p.key == k))
{
return true;
}
slist = slist.suc; //本层没找到,进入下一层找
if(slist.succ == null)
{
return false; //已进入最后一层了
}
if(p.pred != null)
{
p = p.below; //进入下一层对应位置
}
else
{
p = header; //进入下一层从第一项开始
}
}
}
对于任意0 <= k < h,Sk中任意结点在Sk+1中依然出现的概率始终为1/2。即,S0中任意关键码依然在Sk中出现的概率等于2(-k)。因此第k层列表所含结点的期望为:
所以空间整体消耗的期望值为O(n)。平均时间复杂度不超过O(logn)。
插入:
从顶层的首节点出发,查找不大于关键码k的最后一个结点p,若已有雷同结点,则需要强制让p成为其对应在塔底的元素。之后新节点b为紧邻p右侧的新基座。以随机1/2的概率判定新元素是否需要向上生长,如果需要,则找出不低于此高度的最近前驱——若该前驱是header且当前已是最顶层,则必须首先创建新的一层,然后将p转至上一层的header,否则可直接将p提升至该高度,并在该层将新节点插入p之后,b之上,同时该节点也应与该层上原有的处于其后的元素建立连接。
比如插入关键码4:
查询到应该在4后边插入4
用random() & 1的方法得出本次是否需要生长,如果为1则向上长一层,否则停止生长完成插入(书中例子里给出的是前两次为1,第三次为0,所以4就生长到S2为止)
删除:
从顶层的首节点开始查找目标,如果不存在则直接返回,如果存在,则逐层拆除与之对应的塔,过程正好跟上边的插入相反。(因为是删除了整个塔,所以过程中主要调整横向连接,纵向可以忽、省略)
散列表(HashTable)
C# MSDN中的HashTable Class
逻辑上是由一系列可存放词条(或引用)的单元组成,所以这些单元也称作桶单元。各桶单元也应按其逻辑次序在物理上连续排列,所以其底层结构是用数组实现的。此时散列表也称作桶数组,若桶数组容量为R,则其中合法秩的区间[0, R)也称作地址空间。
散列函数:从关键码空间到桶数组地址空间的函数
hash(key) = key
桶数组 A[hash(x)]
(完美散列:在时间和空间达到最优的散列。)
假定关键码均为[0, R)的整数,将词典中的词条数记作N,散列表长度记作M,于是通常有 R>>M>N 。
散列函数设计原则:
确定性;映射过程不能过于复杂;所有关键码经映射后可尽量可覆盖整个地址空间[0, M) ;
(因为R远远大于M,所以关键码不同的词条被映射到同一散列地址的情况——散列冲突难以彻底避免。)
所以关键码映射到各桶的概率应尽量接近1/M。
所以随机性越强,规律性越弱的散列函数越好。
除余法:将散列表长度M取为素数,并将关键码key映射至key关于M整除的余数
hash(key) = key mod M
因为这本残缺的书到这页又又又又没了,所以找了网上别人总结的处理散列冲突的方法:

本文详细介绍了跳转表和散列表这两种高效的数据结构。跳转表是一种由Dictionary和LinkedList派生出来的词典,其查询和维护操作的时间复杂度为O(logn),通过分层的链表结构实现快速搜索。散列表则是利用散列函数来存储和检索词条的桶数组,文中还讨论了散列冲突的解决方案。
1764

被折叠的 条评论
为什么被折叠?



