数据结构预算法 python实现 hash表

哈希表

为了快速的定位到某个元素,科学家给每个元素一个“逻辑下标”,然后直接找到元素,哈希表就是这种实现,他通过一个哈希函数来计算一个元素应该放在哪,当然对于特定的某个元素,哈希函数每次计算的下标必须一样才可以,而且范围不能超出给定数组的长度。
假如我们有一个数组t 包含m=13个元素,我们可以定义一个简单的哈喜函数h(key) = key%m 这里取模函数使得h(key)的结果不会超出数组长度的下标,分别插入以下元素 756,431,142,579,226,903,338
哈希冲突:经过hash算法计算,发生了下标的冲突,该下标下的当前值,会有一个链接,指向发生冲突的值(链接法),缺点,如果冲突比较多,查找时就不是O(1)的时间复杂度
还有一种叫 开放寻址法:他的基本思想是当一个槽被占用的时候,采用一种方式来来找到下一个槽,根据找下一槽的方式不同,分为:
线性探查:当槽被占用,找下一个可用的槽
二次探查:当槽被占用,以二次作为偏移量
双重散列:重新计算哈希结果
python使用的是二次探查法,他的意思就是如果遇到冲突,我们就在原始的位置上增加i
平方
装载因子: 已经被使用的槽数比上总槽数
当空间不够用时,我们就定义一个负载因子的概念,比如插入了8个元素,总槽数为13,就是8/13约等于0.62 。通常来说当负载因子小于0.8,就要新开辟空间,并且重新进行散列
冲哈喜
当负载因子小于0.8时,会重新开辟空间,开辟空间的策略要看具体的底层实现,之后把不为空的槽的数据重新哈希到表中
模拟cpython实现一个哈希表
最常用的三个操作,add,get,remove
槽一般有三种状态:
从未被使用:HashMap.UNUSED
使用过但是remove了:HashMap.EMPTY
正在使用的节点

class Array(object):

    def __init__(self,size=32,inits=None):
       self._size = size
       self._items = inits* size

    def __getitem__(self,index):
        return self._items[index]

    def __setitem__(self,index,value):
        self._items[index] = value

    def __len__(self):
        return self._size

    def clear(self,value = None):
        for i in range(self._items):
            self._items[i] = value

    def __iter__(self):
        for item in self._items:
            yield item

#定义一个槽
class Slot(object):
    """
    定义一个哈希表数组的槽
    注意,一个槽有三种状态,看你能否想明白,相比链接法解决冲突,二次探查法删除一个key更加复杂
    1.从未使用 HashMap.UNUSED.次槽没有被使用和冲突过,查找时只要找到UNUSED就不用再继续探查了
    2.使用过但是remove了,此时hashMpa.BMPTY,该探查点后边的元素仍可是key
    3.槽正在使用Slot点
    """

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


#实现哈希表
class HashTable(object):
    UNUSED = None #没被使用过
    BMPTY = Slot(None,None)#使用过但是删除了

    #初始化函数
    def __init__(self):
        #定义一个数组
        self._table = Array(8,init = HashTable.UNUSED)
        #插入了多少个值
        self.length = 0

    #装饰器,用于计算装载因子
    @property
    def _load_factor(self):
        return self.length / float(len(self._table))

    #hash表的长度
    def __len__(self):
        return self.lenght

    #hsah函数 根据key得到一个数组的下标
    def _hash(self,key):
        #abs返回绝对值 hash返回key的hash值
        return abs(hash(key)) % len(self._table)

    #寻找key
    def _find_key(self,key):
        #调用哈希函数,得到第一个槽的位置
        index = self._hash(key)
        #hash表的长度
        _len = len(self._table)
        #表中槽的init值是否为HashMapp.UNUSED
        while self._table[index] is not HashTable.UNUSED:
            #表中槽的init是否为HashMap.EMPTY
            if self._table[index] is HashTable.EMPTY:
                #重新计算key
                index = (index * 5 + 1) % _len
                #跳过出此次循环
                continue
            #表中有这个key直接输出key
            elif self._table[index].key == key:
                return index
            #否则重新计算key
            else:
                index = (index * 5 + 1) % _len
        #返回None
        return None
    #判断槽能不能被插入
    def _silot_con_insert(self,index):
        return (self._table[index] is HashTable.BMPTY or self._table[index] is HashTable.UNUSED)

    #找到一个空槽的位置
    def _find_slot_for_insert(self,key):
        #获取k的哈希值
        index = self._hash(key)
        #获取到hash表的长度
        _len = len(self._table)
        #如果不能插入,就重新定义key
        while not self._slot_for_insert(index):
            index = (index * 5 + 1) % _len
        return index
    #添加键值对
    def add(self,key,value):

        if key in self:
            #如果有就找到key
            index = self._find_key(key)
            #把value赋值给槽
            self._table[index].value = value
            return False
        else:
            #如果没有找到一个空槽的key
            index = self._find_slot_for_insert(key)
            #这个空槽指向新的槽
            self._table[index] = Slot(key,value)
            #长度加一
            self.length += 1
            #计算装载因子
            if self._load_factor >= 0.8:
                self._rehash()
            return True

    #装载因子小于0.8
    def _rehash(self):
        #把原来的表,赋值给old_table
        old_table = self._table
        #原来的长度剩2,赋值给newsize
        newsize = len(self._table)*2
        #新建一个数组
        self._table = Array(newsize,HashTable.UNUSED)
        self.length = 0
        #遍历原来的数组
        for slot in old_table:
            #判断是否有值
            if slot is not HashTable.UNUSED and slot is not HashTable.BMPTY:
                #找到新key,赋值给index
                index = self._find_slot_for_insert(slot.key)
                #slot赋值到槽中
                self._table[index] = slot
                #长度加一
                self.length += 1
    #取值操作
    def get(self,key,default=None):
        #找到这个key
        index = self._find_key(key)
        #key如果是和None
        if index is None:
            #返回None
            return default
        else:
            #否则槽中value
            return self._table[index].value
    #删除操作
    def remove(self,key):
        #找到key
        index = self._find_key(key)
        #key为None就抛出异常
        if index is None:
            raise KeyError()
        #取到槽中的value
        value = self._table[index].value
        #长度减一
        self.length -=1
        #赋值给key一个状态
        self._table[index] = HashTable.BMPTY
        #赶回值
        return value
    #遍历
    def __iter__(self):
        #循环数组
        for slot in self._table:
            #判断状态
            if slot not in (HashTable.BMPTY,HashTable.UNUSED):
                #生成值
                yield slot.key

猜你喜欢

转载自blog.csdn.net/weixin_44865158/article/details/100777969