数据结构和算法: 散列表

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表

散列表的时间复杂度不是严格的O(1), 因为和多种因素有关, 比如散列函数, 还有就是如果采用链表法处理冲突, 那么最坏情况是所有数据都散列到一个链表中, 此时是O(n).

hash函数有以下几种简单实现的方法

  • 取余法
    常见的对一个数进行取余操作
  • 分组求和法
    如一个电话436-555-4601, 分成2位数(43, 65, 55, 46, 01), 计算后对一个数进行取余
  • 平方取中法
    首先对该项平方, 然后提取一部分数字结果. 如要对21进行hash, 那么平方后是441, 取后两位在进行hash运算

负载因子

计算方法:散列表包含的元素数/位置总数, 比如一个数组有10个位置, 目前里面有4个元素, 那么填装因子就是0.4

填装因子大于1表明元素数超出位置总数, 需要对数组进行拓容

建议: 一旦填装因子大于0.7就调整散列表的长度

要实现散列表, 散列函数很重要, 好的散列函数可以减少冲突

冲突解决

常用的解决hash冲突的方法有以下几种

  1. 开放寻址法(open addressing)
    如果出现了冲突, 就重新探测一个空位置插入. 当数据量较小, 装载因子小的时候适合采用开放寻址法, 因为当负载因子接近1的时候冲突率会很高, 所以使用内存会比较多.
    通过以下三种方法来再次找到空的槽:

    • 线性探测
      从原hash位置顺序移动, 直到找到一个空的槽后插入. 查找的时候也是先计算散列值, 对比是否相等, 如果不相等顺序向下查找, 如果下一个是空位, 说明不在散列表中
    • 二次探测
      类似线性探测, 但是会使用跳过值
    • 随机探测
      随机选择, 直到找到空槽
  2. 重新散列
    准备多个hash函数, 如果一个发生冲突, 执行下一个
  3. 链表法(chaining)
    每个槽都对应一个链表, 把所有散列值相同的元素放到对应的链表中. 插入的时间复杂度是O(1), 查找时O(k), k是该链表的长度. 比较适合大数据量的情况, 而且可以使用红黑树替代链表进行优化.

模拟实现

实现思路: 维护两个列表slotsdata, 通过散列函数来得到存储位置, 然后把key和value存入两个列表的对应位置

# coding:utf-8

class HashTable:
    """
    模拟实现python的字典结构
    """

    def __init__(self):
        self.size = 11  # 通过size来求余数.
        self.slots = [None] * self.size  # 存键
        self.data = [None] * self.size  # 数据域, 存数据

    def put_data(self, key, data, slot):
        if self.slots[slot] == None:
            self.slots[slot] = key
            self.data[slot] = data
            return True
        else:
            if self.slots[slot] == key:
                self.data[slot] = data
                return True
            else:
                return False

    def put(self, key, data):
        """
        通过hash函数得到对size取余之后的存储位置
        """
        slot = self.hash_function(key, self.size)
        result = self.put_data(key, data, slot)
        while not result:
            # 如果有冲突, 重新执行hash函数计算新的位置
            slot = self.rehash(slot, self.size)
            result = self.put_data(key, data, slot)

    def hash_function(self, key, size):
        return key % size

    def rehash(self, old_hash, size):
        """判断指针位置是否有冲突, 有的话加1后重新计算新的存储位置"""
        return (old_hash + 1) % size

    def get(self, key):
        """根据key得到原本的下标位置"""
        start_slot = self.hash_function(key, self.size)
        data = None
        stop = False
        found = False
        position = start_slot
        while self.slots[position] != None and not found and not stop:
            # 如果key能对应上说明没有冲突直接得到
            if self.slots[position] == key:
                found = True
                data = self.data[position]
            # 如果slot对应的值不等于key
            else:
                position = self.rehash(position, self.size)
                if position == start_slot:
                    stop = True
        return data

    def __getitem__(self, key):
        """根据下标获取元素"""
        return self.get(key)

    def __setitem__(self, key, data):
        """设置新的元素"""
        self.put(key, data)

if __name__ == '__main__':
    table = HashTable()
    table[54] = 'cat'
    table[26] = 'dog'
    table[93] = 'lion'
    table[17] = "tiger"
    table[77] = "bird"
    table[44] = "goat"
    table[55] = "pig"
    table[20] = "chicken"
    table[22] = "chicken"
    table[18] = "ql"
    table[19] = "0999"
    print(table.slots)
    print(table.data)
    # 输出, 直接根据下标进行输出
    print(table[54])
    print(table[17])

总结

  • 在最优情况下(没有冲突), 散列表可以达到 O(1) 时间复杂度, 出现冲突后时间复杂度就和负载因子相关了. 所以说使用Hash表进行搜索时并不是严格的O(1)时间复杂度
  • 为什么散列表经常和链表一起使用?
    散列表虽然查找, 删除和插入速度很快, 但是数据都是无序的. 可以结合链表让数据有序, 需要按顺序遍历散列表的数据时, 可以直接扫描练链表

注意

当使用容量不够时需要扩容, 此时需要注意容量大了后原始内容的散列值可能会变. 还要注意扩容过程中的速度问题, 可以参考渐进式扩容

原文地址:https://www.cnblogs.com/zlone/p/11523510.html

时间: 2024-10-10 15:40:43

数据结构和算法: 散列表的相关文章

数据结构与算法——散列表类的C++实现(分离链接散列表)

散列表简介: 散列表的实现常被称为散列.散列是一种用于以常数平均时间执行插入.删除和查找的技术. 散列的基本思想: 理想的散列表数据结构只不过是一个包含一些项的具有固定大小的数组.(表的大小一般为素数) 设该数组的大小为TbaleSize,我们向该散列表中插入数据,首先我们将该数据用一个函数(散列函数)映射一个数值x(位于0到TbaleSize1-1之间):然后将该数据插入到散列表的第x的单元.(如果有多个数据映射到同一个数值,这个时候就会发生冲突) 散列函数介绍: 为了避免散列函数生成的值不是

算法——散列表

散列表 算法——散列表 散列表(hash table):键值(key_value)映射,Python提供的哈希列表实现为字典. 作用: 模拟映射关系 便于查找 避免重复 缓存/记住数据,以免服务器再通过处理来生成它们 # hash_table.py 哈希表 # 避免重复 def vote(li): voters = {} for i in li: if i not in voters: voters[i] = True else: print(i + ' has already voted.')

算法——散列表(最有用的基本数据结构之一)

你作为一个老板,一个卖东西要不断找价格表的销售员和一个一眼看到商品就能知道价格的销售员 你会要哪一个? 可以使用这样形式的数组来记录商品价格 [(eggs,2,49)(milk,1.49)(pear,0.79)], 将这些数组按商品名排序,再执行二分查找商品的价格. 这样查找价格的时间就是O(log n) 什么是散列函数? 输入能够映射到数字的函数. 散列函数需要满足的要求: ①必须是一致的.比如输入apple得到4,那么每次输入apple都是4 ②将不同的输入映射到不同的数字.[最理想] 如果

算法-散列表

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组就是散列表.散列表是算法上时间和空间做出权衡的例子,如果没有内存限制,我们可以直接将键作为数据的索引直接一次访问即可,如果没有时间限制我们直接通过之前的无序数组进行顺序查找即可.散列函数能够直接将关键字转化成索引,但是会出现相同索引的情况,这个时候我们需要处理冲突碰撞,我们会使用到拉链法和

数据结构复习之散列表查找(哈希表)

一.散列表相关概念 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key).公式如下: 存储位置 = f(关键字) 这里把这种对应关系f称为散列函数,又称为哈希(Hash)函数.按这个思想,采用散列技术将记录存在在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表.那么,关键字对应的记录存储位置称为散列地址. 散列技术既是一种存储方法也是一种查找方法.散列技术的记录之间不存在什么逻辑关系,它只与关键字有关,因此,散列主要是面向查

数据结构与算法----散列/哈希

1. 简介 散列表的实现叫散列hashing,散列用于以常数平均时间执行 插入.删除.查找,不支持排序.findMin.findMax. 查找关键字不需要 比较 在一个记录的存储位置和它的关键字之间建立映射关系:key--f(key)   这个关系就是散列函数/哈希函数.将一些记录存储在一块 连续 的存储空间,这块空间就是散列表/哈希表. 与线性表.树.图比较: 数据元素之间没有什么逻辑关系,也不能用连线图表示出来. 问题: 关键字不同,但通过散列函数计算的结果相同,即出现了冲突 collisi

JavaScript数据结构与算法-散列练习

散列的实现 // 散列类 - 线性探测法 function HashTable () { this.table = new Array(137); this.values = []; this.simpleHash = simpleHash; this.betterHash = betterHash; this.showDistro = showDistro; this.put = put; this.get = get; } function put (key, data) { let pos

数据结构之散列表总结

散列表的概念 注意:    ①由同一个散列函数.不同的解决冲突方法构造的散列表,其平均查找长度是不相同的.     ②散列表的平均查找长度不是结点个数n的函数,而是装填因子α(填入表中的记录个数/散列表的槽数    n/m).因此在设计散列表时可选择α以控制散列表的平均查找长度.(平均查找长度=总查找(插入)/记录个数)          通过链接法解决冲突:成功查找的期望查找长度O(1+a), 不成功查找的平均查找长度也为O(1+a).         开放寻址解决冲突:引入探查序列,对于a<

数据结构与算法复习

数据结构回顾与整理 复习资料<数据结构与算法 Javascript描述> 人民邮电出版社 数据结构与算法学的次数再多也不为过. 1,数组array:一个存储元素的线性集合,元素可以通过索引来任意存取,索引通常是数字,来计算元素之间存储位置的偏移量. 除了常用的方法,ES5中新增加了几个迭代方法,forEach(),every()some(),reduce(),map(),filter(). 2.列表list:是一组有序数据.其中的数据项被称为 元素.包含属性/方法有listsize,pos(当