散列表(一).散列表基本内容介绍

  一说到散列表,大家脑子想到的词就是:Hashmap、key-value、查找速度快、增删速度快等等。确实,在我们平常的学习生活中,散列表是很常见、也是用的很多的数据结构。那么散列表是怎样设计出来的,为什么它既可以和数组一样查询快,又可以和链表一样快增删,本节让我们一起了解一下什么是散列表、什么是散列函数、它究竟是如何设计出来的。

散列思想

  什么是散列思想呢?散列表还有一个英文名叫做Hashtable,也叫做“哈希表”、“hash表”,hash我们都了解,是同过一定的算法、hash算法得到一个对象的散列值,用来标识对象本身的算法。

  我们来举一个例子,假如有50个同学参加数学竞赛,为了能快速方便地找到每一个人,所以每个人都设立一个编号,从1到50,代表50个学生。现在如果我们用代码去实现这一功能的话,我们可以将这50个学生放到数组中去,从数组下标为1的位置开始,放入编号为1的学生,以此类推,将学生的编号和数组的下标一一对应,当我们要找第32个学生的时候,直接arr[32]就可以找到这个学生了,这样,就达成了O(1)的时间复杂度。实际上,这个例子已经用到了散列思想,能够快速地找到我们想找的学生,如果你觉得不够明显的话我们可以稍加改造一下。

  假如,老师说编号这样太简单了,无法明显地表面这个学生的信息,需要再加上年级、班级这些信息,变成了6位数字,比如020433,02代表大二年级、04是4班,后两位还是之前的编号,这样我们如何存储学生的编号才能很快地找到对应的学生。

  思路还是那个思路,尽管6位数字作为数组的下标过长,我们可以截取编号的后两位,来作为数组的下标,去读取我们需要的数据。

  这就是典型的散列思想,这里参赛选手的编号我们称作键(key),用来标识学生,学生本身这个对象我们称为(value)。我们把参赛编号映射为数组下标的映射方法叫做散列函数hash函数),而经过散列函数得到的值就叫做散列值(hash值)。

  通过上面的例子我们可以总结到:散列表用的就是数组支持按照下标访问数据时时间复杂度为O(1)的特性来实现的高效访问,当存储数据时,通过散列函数得到数组的下标然后将数据放入到该位置中去,然后进行读取。可以看出,散列表的实质是数组,是一种升级版的数组。

散列函数

  散列函数顾名思义是个函数,用函数表示就是hash(key)。那么大家想一下,要编写一个hash函数需要注意哪些问题呢,hash函数需要满足什么呢?

  1. 散列值是一个非负整数

  2. 如果key1 = key2 ,那么hash(key1) == hash(key2)

  3. 如果key1 != key2,那么hash(key1) != hash(key2)

  解释一下这三点,第一点很明显,数组的下标是从0开始的,肯定是非负的,

   第二点,key一样,他们的hash值也一样,也是很正确的,因为数组一个下标只能对应一个值。

  但是第三点我们却无法满足,当两个key的值不同的时候,他们的hash值不敢保证一定不一样。即使是著名的MD5、SHA3这些hash算法,也无法到达这一目的,所以,以数组构成的散列表,存在着散列冲突的问题。并且数组的长度有限,随着值的增多,散列冲突的概率也会大大增大。

  我们目前无法找到一个完美地能够解决hash冲突的办法,所以,我们为了解决散列冲突,提供了以下几种思路方法。

散列冲突

  刚才我们说到,再完美的hash函数也无法解决散列冲突的问题,那么,我们该如何去解决散列冲突的问题呢?常用的解决方法有两类:开放寻址法(open addressing)和链表法(chaining)。

1.开放寻址法

开放寻址法的思想是,如果出现了散列冲突,就重新寻找一个空闲的位置去存放该数据,那么如何去探测空闲位置呢?

这里,我们说一个比较简单的线性探测法,比如我们通过散列函数计算出了散列值为6,但是数组这个位置已经有数据了,然后我们就从7开始一直往后找,直到找到空闲的位置,然后插入该元素。

但是,这里我们会遇到一些问题,比如删除的时候,我们不能单纯地把要删除的位置设置为空,为什么呢?

因为如果这个数据本来存在,但是因为线性探测法的原因它被安排在了其他位置,当查询的时候我们会判定它为不存在。这个问题该如何解决呢?

我们可以将删除的元素,特殊标记为 deleted。当线性探测查找的时候,遇到标记为deleted的空间时,并不是停下来,而是继续往下探测。

结论:

大家肯定已经发现了,线性探测法存在很多的问题。当散列表中的数据越来越多的时候,散列冲突发生的可能性就会越来越大,空闲的位置会越来越少,查找的时间就会越来越长。最坏情况下,查找的效率会退化为O(n),所以在数据比较多的时候,装载因子较少的时候才会去使用开发寻址法。

什么是装载因子呢?

我们用装载因子来表示散列表的装满程度,也就是空闲状态。公式为:

散列表的装载因子 = 填入表中的元素个数 / 散列表的长度

装载因子越大,说明空闲越少,可能发生的冲突越多。

2..链表法

链表法是跟为常用的解决散列冲突的办法,学java的小朋友们应该都知道,hashmap就是通过链表法来解决的hash冲突。

原理很简单,就是通过散列函数得到的目标位置如果已经有数据的话,就形成一个链表,将冲突的数据放入链表中去,这样插入的时间复杂度依旧是O(1),但是当链表长度过长的时候,查询数据的速率会变慢,所以在有些时候,链表会转化为树来减少查找的时间。如图所示,hashmap用链表来解决hash冲突

应用场景

接下来,让我们看两个例子,感受一下哈希表是如何应用的

1.假设我们有 10 万条 URL 访问日志,如何按照访问次数给URL进行排序

这个问题可以分为两步,第一步是统计每个URL的访问次数,第二步根据访问次数进行排序。那具体怎么做呢?

首先我们以URL为key,访问次数为value,存入散列表中。时间O(N)。然后根据访问次数从大到小排序,用快速排序为O(NlogN)。

2.有两个字符串数组,每个数组大约有10万条字符串,如何快速找出两个数组中相同的字符串?

以第一个字符串数组构建散列表,字符串为key,出现次数为value。再遍历第二个字符串数组,在散列表中查找,如果value大于0,说明存在该字符串,时间复杂度为O(N)

内容小结

今天讲了哈希表一些简单的构成、设计思想、散列函数、散列冲突等理论性知识。

散列表是数组的进化版,利用了数组支持按照下标快速访问元素的特性,达到了O(1)的时间复杂度。散列表主要的问题是散列冲突问题,常见的两种方法是开放寻址法和链表法,其中链表法是常用的解决散列冲突的方法。

散列函数的设计决定了散列冲突的概念,也就决定了散列表设计的好坏

原文地址:https://www.cnblogs.com/GodHeng/p/10212058.html

时间: 2024-10-24 05:05:51

散列表(一).散列表基本内容介绍的相关文章

数据结构--散列排序--散列表

散列表 散列查找,我们又回到了查找, 编译的时候,涉及变量及属性的管理: 插入:新变量的定义 查找:变量的引用 实际上是动态查找问题,查找树AVL树. 两个变量名(字符串)比较效率不高.字符串的比较要一个一个的比下去,时间会比较长, 是否可以把字符串转换成数字,再处理,就快多了.就是散列查找的思想. 已知的查找方法: 顺序查找                                          O(N) 二分查找(静态查找,不适合动态查找)   O(log2N) 二叉搜索数    

字典:散列表、散列字典、关键字列表、集合与结构体

字典 散列表和散列字典都实现了Dict的行为.Keyword模块也基本实现了,不同之处在于它支持重复键. Eunm.into可以将一种类型的收集映射转化成另一种. defmodule Sum do def values(dict) do dict |> Dict.values |> Enum.sum end end hd = [ one: 1, two: 2, three: 3 ] |> Enum.into HashDict.new IO.puts Sum.values(hd) #=&g

python的列表,元组和字典简单介绍

引 入 java                                   python 存取多个值:数组或list集合 ------------------------> 列表,元组 key-value格式:    Map        ------------------------>    字典 自己学习发现,java跟python这两门面向对象语言在数据类型的定义上,很多思想都是互通的,这里不说java,简单介绍一下python的列表,元组和字典. 一.列表 List: 最通

phpcms v9栏目列表调用每一篇文章内容方法1

我们先来看下默认栏目调用的代码: 复制代码代码如下:{pc:content action="lists" catid="$catid" num="25" order="id DESC" page="$page"}<ul class="list lh24 f14">{loop $data $r}<li><span class="rt">

html的有序列表和无序列表简单介绍

html的有序列表和无序列表简单介绍: 本章节将会简单一下有序列表和无序列表的一些简单用法,希望能够给需要的朋友带来一定的帮助. 一.有序列表: 使用<ol>标签就可以定义一个有序列表,之所以称其为有序列表,就是因为可以使列表具有排序功能. 利用<ol>的type属性就可以实现有序排序功能. type属性值: 1表示以 1,2,3,4 来表示. a 表示以 a,b,c,d 来表示. A 表示以 A,B,C,D 来表示. i 表示以 i,ii ,iii 来表示. I 表示以 I,II

python里list列表,tuple元组内部功能介绍

list列表 append            #在列表尾部追加元素 clear                #把列表清空 count               #统计元素出现的次数 extend             #合并两个列表或者元组与列表合并,对原来列表的扩展 index                #获取元素的下标,索引 insert                #插入元素,可以自己指定插入位置 pop                   #删除某个元素,默认删除最后一个

Java散列和散列码的实现

转自:https://blog.csdn.net/al_assad/article/details/52989525 散列和散列码 ※正确的equals方法应该满足的的条件: ①自反性:x.equals(x) 一定返回true: ②对称性:y.euqlas(x)为true,那么x.equals(y)一定为true: ③传递性:x.equals(y)为true,y.euqlas(z)为true,则z.equals(x)为true: ④一致性:如果x,y中用于等价比较的信息没有变化,那么无论调用y.

容器深入研究 --- 散列与散列码(二)

为速度而散列: SlowMap.java说明了创建一个新的Map并不困难.但正如它的名称SlowMap所示,它不会很快,如果有更好的选择就应该放弃它.它的问题在于对键的查询,键没有按照任何特定的顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式. 散列的价值在于速度: 散列使得查询得以快速进行.由于瓶颈在于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询. 散列则更进一步,它将键保存在某处,以便能够很快的找到.

java 散列与散列码探讨 ,简单HashMap实现散列映射表运行各种操作示列

package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象联系起来, * @author lenovo * */ //土拨鼠 public class Groundhog { protected int number; public Groundhog(int n) { number=n; } @Override public String toString() { return "Groundhog #" + number

java 散列与散列码探讨 ,简单HashMap实现散列映射表执行各种操作示列

package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象联系起来, * @author lenovo * */ //土拨鼠 public class Groundhog { protected int number; public Groundhog(int n) { number=n; } @Override public String toString() { return "Groundhog #" + number