基于python的数据结构之【哈希表ADT】

在python中的 dict 和 set 查找速度很快,内部就是散列表(也叫作哈希表)

  • 数组通过下标访问,时间O(1),删除要O(n)

  • 链表遍历访问,时间O(n)

  • 哈希表就是一种快速定位和删除元素的方法。

    • 除了利用元素下标找到,还有一种就是找到【逻辑下标】,再找到这个元素。他通过哈希函数计算一个元素应该放在哪个位置,然后对于一个特定的元素,哈希函数每次计算的下标要一样,而且范围不能超过给定的数组长度
    • 举例:
      • 有一个数组T,包含M=13个元素,定义一个哈希函数h
      • h(key) = key % M
        • 这样取模运算使得结果在数组范围内
  • 哈希冲突(collision):

    • 解决办法
      • 链接法(chaining)
        • 在当前位置多出一条支链,进行数据存储
        • 而如果链接过长,时间复杂度会退化变复杂
      • 开放寻址法(open addressing)
        • 线性探查(linear probing)
          • 当一个槽被占用,找下一个可用的槽
          • h(k, i)=(h'(k)+i)%m, i==0,1,...,m-1
            • k是插入的值,i是位置,m是
        • 二次探查(quadratic probing)
          • 一个槽被占用,以二次方作为偏移量
          • h(k, i)=(h'(k)+c_1+c_2*i**2)%m, i=0,1,...,m-1
          • c_1,c_2常数,i**2; c_1+c_2*i**2偏移量
        • 双重散列(double hashing)
          • 重新计算结果
          • h(k,i)=(h_1(k)+ih_2(k))%m
    • 哈希函数
      • 上边用的是取模的简单函数
    • 装载因子(load factor)
      • 哈希表大小为13,槽数被占用4,则load factor为 4/13
      • 当装载因子超过0.8,就要新开辟空间并且重新进行散列
    • 重哈希(Rehashing)
      • 装载因子超过0.8,扩容然后重新哈希操作
  • HashTable ADT

# -*- coding:utf-8 -*-
# Author:        Greed_Vic(PL Z)
# Product_name:  PyCharm
# File_name:     Hashtable 
# @Time:         15:26  2021/6/10
class Array(object):
    """
    定义一个数组
    """

    def __init__(self, size=32, init=None):
        self._size = size  # 设置数组长度
        self._items = [init] * size  # 开辟数组,初始None

    def __getitem__(self, index):
        return self._items[index]  # 特殊方法,用来获取此下标的元素

    def __setitem__(self, index, value):
        self._items[index] = value  # 特殊方法,用来赋值

    def __len__(self):
        return self._size  # 特殊方法,获取长度 接受 len(array)

    def __iter__(self):
        for item in self._items:  # 特殊方法, 接受迭代
            yield item

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


class Slot(object):
    """
    定义一个槽,用来装入array的
    """

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


class HashTable(object):
    """
    三种状态的表,没用过的,空的,以及用过的
    """
    UNUSED = None  # unused
    EMPTY = Slot(None, None)  # used but deleted

    def __init__(self):
        self._table = Array(8, init=HashTable.UNUSED)  # init an array [None,None,...,None]
        self.length = 0  # init length

    @property  # 很方便的创建只读属性 √
    def _load_factor(self):
        return self.length / float(len(self._table))

    def __len__(self):  # 求哈希表的占用空间
        return self.length

    def _hash(self, key):  # 哈希函数,求对应下标
        return abs(hash(key)) % len(self._table)

    def _find_key(self, key):
        """
        用来根据所给的key来进行同样的hash操作找到下标
        :param key:
        :return: 下标or None
        """
        index = self._hash(key)  # 第一次哈希操作
        _len = len(self._table)  # 求当前哈希表总大小
        while self._table[index] is not HashTable.UNUSED:  # 当前的下标索引的空间不是未使用空间
            if self._table[index] is HashTable.EMPTY:  # 当前空间是被使用但是被remove了的空间
                index = (index * 5 + 1) % _len  # cpython 使用的一种解决哈希冲突的方式
                continue
            elif self._table[index].key == key:  # 如果存在这个key,返回当前下标
                return index
            else:
                index = (index * 5 + 1) % _len
        return None  # 不符合条件的key,返回None

    def _slot_can_insert(self, index):
        """
        :param index:
        :return: 当前槽的两种状态是能够插入的
        """
        return (self._table[index] is HashTable.EMPTY
                or self._table[index] is HashTable.UNUSED)

    def _find_slot_for_insert(self, key):
        """
        :param key:
        :return:寻找能够插入的槽的下标
        """
        index = self._hash(key)  # 哈希操作
        _len = len(self._table)  # 哈希表总大小
        while not self._slot_can_insert(index):  # 当前下标不可用作插入的槽
            index = (index * 5 + 1) % _len  # 解决哈希冲突
        return index

    def __contains__(self, key):
        """
        :param key:
        :return: 查找key是否存在
        """
        index = self._find_key(key)
        return index is not None

    def add(self, key, val):
        """
        用来找到对应下标,然后进行插入
        :param key:
        :param val:
        :return:
        """
        if key in self:  # key存在,获取下标
            index = self._find_key(key)
            self._table[index].val = val  # 进行赋值
            return False
        else:  # key不存在
            index = self._find_slot_for_insert(key)  # 找到可以插入的下标
            self._table[index] = Slot(key, val)  # 进行插入
            self.length += 1  # 占用长度加一
            if self._load_factor >= 0.8:  # 装载因子达到条件
                self._rehash()  # 进行重新哈希操作

    def _rehash(self):  # 重哈希操作
        old_table = self._table  # 复制老的哈希表
        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.EMPTY:
                index = self._find_slot_for_insert(slot.key)
                self.length += 1
                self._table[index] = slot

    def get(self, key, default=None): 
        index = self._find_key(key)  # 找到对应下标
        if index is None:  
            return default
        else:
            return self._table[index].val  # 返回对应的值   这里面试 slot(key, val) 的结构

    def remove(self, key):
        index = self._find_key(key)  # 找到对应的下标
        if index is None:
            raise KeyError()
        val = self._table[index].val  # 获取将要remove的值
        self.length -= 1 
        self._table[index] = HashTable.EMPTY  # 被remove 定义为空的槽
        return val

    def __iter__(self):
        """
        可进行迭代的特殊方法
        :return: 只迭代回key
        """
        for slot in self._table:
            if slot not in (HashTable.EMPTY, HashTable.UNUSED):
                yield slot.key


if __name__ == '__main__':
    """
    单测
    """
    def test():
        h = HashTable()
        h.add('a', 0)
        h.add('b', 1)
        h.add('c', 2)
        assert len(h) == 3
        assert h.get('a') == 0
        assert h.get('b') == 1
        assert h.get('asd') is None

        h.remove('a')
        assert h.get('a') is None
        assert sorted(list(h)) == ['b', 'c']

        n = 50
        for i in range(n):
            h.add(i, i)

        for i in range(h):
            assert h.get(i) == i
    test()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JamePrin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值