用Python实现数据结构之映射(2)

def hash_code(s):
    mask = (1 << 32) - 1
    h = 0
    for character in s:
        h = (h << 5 & mask) | (h >> 27)
        h += ord(character)
    return h


<<是左移,>>是右移,&是按位与,|是按位或,ord()函数返回一个字符的ascii码或unicode值

Python中的哈希码

Python中提供了hash()函数,传入对象x,返回一个整型值作为它的哈希码

在Python中只有不可变类型才可以使用hash,如果把我们自定义的对象作为参数传入则会报错。

若想让我们自定义的对象能够使用,可以在类中实现一个叫做hash的特殊方法,在该函数中调用hash函数,并传入该对象的一些不可变属性组合,将值再返回,例如:

def __hash__(self):

  return hash((self.red,self.green,self.blue))


2.压缩函数

划分方法

要把哈希码映射到[0,N-1]的区间中,最简单的方式就是进行求余数,例如f(i) = i modN

可是这样显然会有大量的冲突,一种稍微能够减小冲突的办法是将N改为一个素数

这样能够得到一些改善,但是pN+q类型的哈希码还是会被压缩成同一个数

MAD方法

MAD即Multiply-Add-and-Divide,这个方法通过下面这个式子进行映射

[(ai+b) mod p] mod N

N是区间的大小,p是比N大的素数,a和b是从区间[0,p-1]任意选择的整数且a>0

这个函数会尽可能的使映射均匀的分配到[0,N-1]中

3.冲突处理

尽管在求哈希值与压缩函数的过程中我们尽可能避免发生冲突,但还是会有可能造成冲突的,为此还需要进行冲突的处理

使用二级容器

把列表中的每一项都存储为一个二级容器,将映射到该项的键值存入到二级容器中,查找键时先定位到二级容器,再在二级容器中寻找。这里的二级容器的效率要求就不是那么高了,可以使用上文中最开始定义的映射的简单实现来做这个二级容器。在整个哈希表中,我们希望存储的键值项的数量n小于N,也就是n/N<1,n/N叫做这个哈希表的负载因子。

线性探测

这个简单说就是如果映射到这个地方已经有其他键值占上了,那么就向它的后一位放,如果后一位也有了,就继续向后放,知道找到一个空位,然后放进去。

查找的时候,映射到一个位置时要判断一下是不是要找的那个key,如果不是就向后一位找,知道找到是相同键了或者出现空位了,就停止

删除的时候,一样是先找到,然后为了不影响查找,不能简单的将其设置为空,应该用一个标记的对象填住该位置,这时查找的方法也要进行一些改动使其能够跳过这种标记位置。

这种方法的缺点是每一对键值会连续的存储,这种聚集的现象会导致效率的问题。

二次探测

为了改善线性探测聚集现象的发生,原先采用的(j+i)mod N(j为压缩函数得出的值,i为1,2,3….)探测方式改为(j+i^2)mod N

但是当元素超过了哈希表的一半时,这种方式无法保证找到空闲的位置。而且这种方式的删除或其他操作也会更复杂

双哈希策略

这种方式选择了再次进行哈希,如将探测方式改为(j+i*h(k))mod N,h()为一个哈希函数,k为键。

Python字典所采用的方式

字典采用的是(j+f(i))mod N的方式,f(i)是一个基于伪随机数产生器的函数,它提供一个基于原始位的可重复的但是随机的,连续的地址探测序列。

用Python具体实现

首先是一个哈希表的基类,采用MAD的压缩函数

class HashMapBase(MutableMapping):
    """哈希表的基类,需要在子类中实现_inner_getitem,_inner_setitem,
    _inner_delitem与__iter__"""

class item():

def __init__(self, key, value):
            self.key = key
            self.value = value

def __eq__(self, other):
            return self.key == other.key

def __ne__(self, other):
            return self.key != other.key

def __init__(self,cap=11,p=109345121):
        self._table = cap*[None]
        self._n = 0          # 元素数量
        self._prime = p      # MAD中的参数
        self._scale = 1 + random.randrange(p+1)    # MAD中的参数
        self._shift = random.randrange(p)    # MAD中的参数

def _hash_func(self,key):
        return (hash(key)*self._scale+self._shift)%self._prime%len(self._table)

def __len__(self):
        return self._n

def __getitem__(self, k):
        j = self._hash_func(k)
        return self._inner_getitem(j,k)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/fcb8ddb6973bc8b0c04ad0f77bd7889f.html