数据结构和算法: 散列表
时间:2019-09-15
本文章向大家介绍数据结构和算法: 散列表,主要包括数据结构和算法: 散列表使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
散列表(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冲突的方法有以下几种
- 开放寻址法(open addressing)
如果出现了冲突, 就重新探测一个空位置插入. 当数据量较小, 装载因子小的时候适合采用开放寻址法, 因为当负载因子接近1的时候冲突率会很高, 所以使用内存会比较多.
通过以下三种方法来再次找到空的槽:- 线性探测
从原hash位置顺序移动, 直到找到一个空的槽后插入. 查找的时候也是先计算散列值, 对比是否相等, 如果不相等顺序向下查找, 如果下一个是空位, 说明不在散列表中 - 二次探测
类似线性探测, 但是会使用跳过值 - 随机探测
随机选择, 直到找到空槽
- 线性探测
- 重新散列
准备多个hash函数, 如果一个发生冲突, 执行下一个 - 链表法(chaining)
每个槽都对应一个链表, 把所有散列值相同的元素放到对应的链表中. 插入的时间复杂度是O(1), 查找时O(k), k是该链表的长度. 比较适合大数据量的情况, 而且可以使用红黑树替代链表进行优化.
模拟实现
实现思路: 维护两个列表slots
和data
, 通过散列函数来得到存储位置, 然后把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
- 使用MagicAjax 实现无刷新Webparts
- Python 项目实践一(外星人入侵小游戏)第五篇
- Python 项目实践一(外星人入侵小游戏)第三篇
- WordPress自定义栏目运用实例 VI:设置外链缩略图/特色图像
- Python 项目实践一(外星人入侵小游戏)第二篇
- IBatisNet配置
- WordPress设置评论到达一定数量后自动关闭评论功能
- Python 项目实践一(外星人入侵)第一篇
- 外媒:域名Covermate.com超35万终端交易
- 移除WordPress 管理后台的主题编辑功能
- 百度区域链开放平台“BaaS”启用了二级子域名chain.baidu.com
- Qt界面UI之QML初见(学习笔记四)
- 清新唯美的jQuery天气预报插件(网页天气预报插件)
- 32和64位的CentOS 6.0下 安装 Mono 2.10.8 和Jexus 5.0
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 详解flutter engine 那些没被释放的东西
- Centos7安装PHP及Nginx的教程详解
- Flutter Image实现图片加载
- Centos7系统下搭建.NET Core2.0+Nginx+Supervisor环境
- CentOS7 LNMP+phpmyadmin环境搭建 第二篇LNMP环境搭建教程
- 详解Linux 下开发微信小程序安装开发工具
- Linux一个增强的截图及分享工具:ScreenCloud
- linux 命名管道实例详解
- 11个JavaScript代码重构最佳实践
- Linux中在不破坏磁盘的情况下使用dd命令
- Jexus开机自动启动配置方法
- 在 CentOS 7 中安装 MySQL 8 的教程详解
- Centos7.2 编译安装PHP7.0.2的步骤
- Linux系统下为Nginx安装多版本PHP
- Linux系统下多版本php共存的解决方案(超简单)