• python数据结构之哈希表


     

    哈希表(Hash table)

    使用哈希表可以进行非常快速的查找操作,查找时间为常数,同时不需要元素排列有序

    python的内建数据类型:字典,就是用哈希表实现的。

    python中的这些东西都是哈希原理:字典(dictionary)、集合(set)、计数器(counter)、默认字典Defaut dict)、有序字典(Order dict)

    本篇笔记先把哈希的原理用类与对象的方式实现。学习笔记资源来自:算法分析哈希表学习笔记

    为了解释哈希表的工作原理,我们来尝试在不使用字典的情况下实现哈希表结构,我们需要定义一个包含 键->值 映射 的数据结构,同时实现以下两种操作:

    add(k, v):

      Add a new item that maps from key k to value v.

      With a Python dictionary,d, this operation is written d[k] = v.

    get(target):

      Look up and return the value that corresponds to key target.

      With a Python dictionary, d, this operation is written d[target] or d.get(target).

    一种简单的实现方法

    建立一个线性表,使用元组来实现 key-value 的映射关系

    复制代码
    """ 
    线性表结构
    """
    class LinearMap(object):
    
        def __init__(self):
             self.items = []
         
        # 往表中添加元素    
        def add(self, k, v):  
            self.items.append((k,v))
    
        # 线性方式查找元素
        def get(self, k): 
            for key, value in self.items:      
                if key == k:      # 键存在,返回值,否则抛出异常
                    return value
            raise KeyError
    
    '''
    我们可以在使用add添加元素时让items列表保持有序,而在使用get时采取二分查找方式,时间复杂度为O(log n)。 
    然而往列表中插入一个新元素实际上是一个线性操作,所以这种方法并非最好的方法。
    同时,我们仍然没有达到常数查找时间的要求。 '''
    复制代码

    改进版本:

    尽管get操作的增长依然是线性,但BetterMap类使得我们离哈希表更近一步

    复制代码
    '''
    将总查询表分割为若干段较小的列表,比如100个子段。
    通过hash函数求出某个键的哈希值,再通过计算,得到往哪个子段中添加或查找。
    相对于从头开始搜索列表,时间会极大的缩短。
    '''
    class BetterMap(object):
          #利用LinearMap对象作为子表,建立更快的查询表
        def __init__(self,n=100):
            self.maps = []          # 总表格
            for i in range(n):      # 根据n的大小建立n个空的子表
                self.maps.append(LinearMap())
          
        def find_map(self,k):       # 通过hash函数计算索引值
            index = hash(k) % len(self.maps)
            return self.maps[index] # 返回索引子表的引用     
     
         # 寻找合适的子表(linearMap对象),进行添加和查找
        def add(self, k, v):
            m = self.find_map(k)        
            m.add(k,v)
         
        def get(self, k):
            m = self.find_map(k)
            return m.get(k)
    复制代码

    由于每个键的hash值必然不同,所以对hash值取余的值基本也是不同的;

    当n=100时, BetterMap的查找速度大约是LinearMap的100倍。

    Hashtable的实现

    复制代码
    class HashMap(object):
        def __init__(self):
            # 初始化总表为,容量为2的表格(含两个子表)
            self.maps = BetterMap(2)
            self.num = 0        # 表中数据个数
          
        def get(self,k):        
            return self.maps.get(k)
          
        def add(self, k, v):
            # 若当前元素数量达到临界值(子表总数)时,进行重排操作
            # 对总表进行扩张,增加子表的个数为当前元素个数的两倍!
            if self.num == len(self.maps.maps): 
                self.resize()
             
            # 往重排过后的 self.map 添加新的元素
            self.maps.add(k, v)
            self.num += 1
             
        def resize(self):
            #重排操作,添加新表, 注意重排需要线性的时间
            # 先建立一个新的表,子表数 = 2 * 元素个数
            new_maps = BetterMap(self.num * 2)
             
            for m in self.maps.maps:  # 检索每个旧的子表
                for k,v in m.items:   # 将子表的元素复制到新子表
                    new_maps.add(k, v)
             
            self.maps = new_maps      # 令当前的表为新表
    复制代码

    重点关注 add 部分,该函数检查元素个数与BetterMap的大小,如果相等,则“平均每个LinearMap中的元素个数为1”,然后调用resize方法。

    resize创建一个新表,大小为原来的两倍,然后对旧表中的元素“rehashes 再哈希”一 遍,放到新表中。

    resize过程是线性的,听起来好像很不怎么好,因为我们要求的hashtable具有常数时间。但是,要知道我们并不需要经常进行重排操作,所以add操作在绝大部分时间中都是常数的,偶然出现线性。由于对n个元素进行add操作的总时间与n成比例,所以每次add的平均时间就是一个常数!

    假设我们要添加32个元素,过程如下:

    1. 由于初始长度为2,前两次add不需要重排,第1,2次 总时间为 2

    2. 第3次add,重排为4,耗时2,第3次时间为 3

    3. 第4次add,耗时1    到目前为止,总时间为 6

    4. 第5次add,重排为 8,耗时4,第5次时间为5

    5. 第6~8次   共耗时3      到目前为止,总时间为 6+5+3 = 14

    6. 第9次add,重排16,  耗时8,第9次时间为9

    7. 第10~16次,共耗时7, 到目前为止,总时间为 14+9+7 = 30

    在32次add后,总时间为62的单位时间,由以上过程可以发现一个规律,在n个元素add之后,当n为2的幂,则当前总单位时间为 2n-2,所以平均add时间绝对小于2单位时间。

    当n为2的幂时,为最合适的数量,当n变大之后,平均时间为稍微上升,但重要的是,我们达到了O(1)。

    Github地址:https://github.com/kumataahh
  • 相关阅读:
    由吃饺子想到的多线程情况下的数据共享问题
    关于伪静态的几个体会
    最近改造的一款可多选的日历插件,已通过兼容性测试
    对kingthy创作的Vtemplate模板引擎的使用心得
    从前辈们整理的数据库优化经验中得到的一点心得分享
    关于近期对Lucene.Net应用研究学习的总结
    对SharpICTCLAS 1.0的一点小小的修改记录
    转 Blob、DataURL、canvas、image的相互转换
    节日_100
    模板生成_100
  • 原文地址:https://www.cnblogs.com/anita-harbour/p/9328573.html
Copyright © 2020-2023  润新知