Day18

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

马上过年了,争取在年前把这本书看完,还有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层列表所含结点的期望为:
第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

因为这本残缺的书到这页又又又又没了,所以找了网上别人总结的处理散列冲突的方法:

解决Hash冲突的几种方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值