教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 golang之哈希表:散列查找算法

golang之哈希表:散列查找算法

发布时间:2022-01-19   编辑:jiaochengji.com
教程集为您提供golang之哈希表:散列查找算法等资源,欢迎您收藏本站,我们将为您提供最新的golang之哈希表:散列查找算法资源
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"/></svg><h1>线性查找</h1>

我们要通过一个<code>key</code>来查找相对的<code>value</code>。有一种最简单的方式,就是将键值对存放在链表里,然后遍历链表来查找是否存在<code>key</code>,存在则更新<code>key</code>对应的<code>value</code>,不存在则将<code>key-value</code>链接到链表上。

这种链表查找,最坏的时间复杂度为<code>O(n)</code>

<h1>
散列查找</h1>

有一种算法叫做散列查找,也称hash查找,是一种空间换时间的查找算法,依赖的数据结构称为哈希表或者散列表:<code>HashTable</code>

<blockquote>

散列表(Hash table,也叫哈希表),是根据关键码值(Key
value)而直接进行访问的数据结构。也就是说,它通过dao把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

</blockquote> <blockquote>

Hash 算法虽然是一种算法,但更像一种思想,没有一个固定的公式,只要符合这种思想的算法都称 Hash 算法。

</blockquote>

散列查找,主要是将<code>key</code>进行<code>hash</code>计算得出一个大整数,然后与数组长度进行取余,这样一个比较大的域空间就只会映射到数组的下标范围,利用数组索引块的特征,用空间换时间的思路,使得查找的速度快于线性查找

首先有一个大数组,每当存一个键值对时,先把键进行哈希,计算出的哈希值是一个整数,使用这个整数对数组长度取余,映射到数组的某个下标,把该键值对存起来,取数据时按同样的步骤进行查找。

有两种方式实现哈希表:线性探测法和拉链法。

<h1>
hash函数</h1> <h2>哈希函数的作用: 根据key获取hash值</h2>


当hash冲突不严重的时候,查找某个键,只需要求hash值,然后取余,定位到数组的某个下标即可,时间复杂度为<code>O(1)</code>

当hash冲突十分严重的时候,每个数组元素对应的链表会越来越长,即使定位到数组的某个下标,也要遍历一条很长很长的链表,就退化为查找链表了。时间复杂度为<code>O(n)</code>

所以hash表实现要解决的问题就是寻找相对均匀,具有很好的随机分布性的hash函数。

Golang语言实现的哈希函数参考了以下两种哈希算法:

xxhash:https://code.google.com/p/xxhash
cityhash: https://code.google.com/p/cityhash
当然还有其他哈希算法如MurmurHash:https://code.google.com/p/smhasher。

还有哈希算法如Md4和Md5等。

因为研究均匀随机分布的哈希算法,是属于数学专家们的工作,我们在此不展开了。

我们使用号称计算速度最快的哈希xxhash,我们直接用该库来实现哈希:https://github.com/OneOfOne/xxhash:

<pre><code class="lang-go hljs"><span class="token keyword">package</span> main <span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"github.com/OneOfOne/xxhash"</span> <span class="token punctuation">)</span> <span class="token comment">// 将一个键进行Hash</span> <span class="token keyword">func</span> <span class="token function">XXHash</span><span class="token punctuation">(</span>key <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">)</span> <span class="token builtin">uint64</span> <span class="token punctuation">{</span> h <span class="token operator">:=</span> xxhash<span class="token punctuation">.</span><span class="token function">New64</span><span class="token punctuation">(</span><span class="token punctuation">)</span> h<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span> <span class="token keyword">return</span> h<span class="token punctuation">.</span><span class="token function">Sum64</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> keys <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"hi"</span><span class="token punctuation">,</span> <span class="token string">"my"</span><span class="token punctuation">,</span> <span class="token string">"friend"</span><span class="token punctuation">,</span> <span class="token string">"I"</span><span class="token punctuation">,</span> <span class="token string">"love"</span><span class="token punctuation">,</span> <span class="token string">"you"</span><span class="token punctuation">,</span> <span class="token string">"my"</span><span class="token punctuation">,</span> <span class="token string">"apple"</span><span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> key <span class="token operator">:=</span> <span class="token keyword">range</span> keys <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"xxhash('%s')=%d\n"</span><span class="token punctuation">,</span> key<span class="token punctuation">,</span> <span class="token function">XXHash</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre>

输出:

<pre><code class="lang-go hljs"><span class="token function">xxhash</span><span class="token punctuation">(</span><span class="token string">'hi'</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token number">16899831174130972922</span> <span class="token function">xxhash</span><span class="token punctuation">(</span><span class="token string">'my'</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token number">13223332975333369668</span> <span class="token function">xxhash</span><span class="token punctuation">(</span><span class="token string">'friend'</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token number">4642001949237932008</span> <span class="token function">xxhash</span><span class="token punctuation">(</span><span class="token string">'I'</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token number">12677399051867059349</span> <span class="token function">xxhash</span><span class="token punctuation">(</span><span class="token string">'love'</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token number">12577149608739438547</span> <span class="token function">xxhash</span><span class="token punctuation">(</span><span class="token string">'you'</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token number">943396405629834470</span> <span class="token function">xxhash</span><span class="token punctuation">(</span><span class="token string">'my'</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token number">13223332975333369668</span> <span class="token function">xxhash</span><span class="token punctuation">(</span><span class="token string">'apple'</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token number">6379808199001010847</span> </code></pre>

我们已经寻找到了计算较快,且均匀随机分布的哈希算法xxhash了,接下来就需要根据hash值映射到哈希数组了。

<h2>
根据hash值映射到哈希数组</h2>

拿到哈希值之后,我们要将这个值映射到数组的某一个空间,也就是进行数组寻址。如果数组的长度为len,那么<code>index = xxhash(key) % len</code>。

可以看出,不管是从初始化哈希数组还是根据值映射哈希数组,我们都需要先确定哈希数组的长度,那么数组的长度应该如何选择?

<h1>
hash函数为什么要选择对素数求余?</h1>

引出此问题,是看到一篇有关jdk中HashMap和Hashtable对于hash算法的选择。

1、位运算(&)比模运算(%)效率高很多,原因是位运算直接对内存数据进行操作,不需要像模运算一样转成十进制,因此处理速度快。

2、<code>HashMap</code>中对key求完hash值,在进行数组寻址时,使用的方法是位运算(代替的取模运算)。公式如下:

<pre><code class="lang-go hljs"> <span class="token punctuation">(</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&</span> hash <span class="token comment">// length为HashMap的容量,是2的n次方</span> </code></pre>

3、<code>HashMap</code>我们一般选择2^x作为hash数组的长度, 是因为我们可以使用&代替%来进行数组寻址

<pre><code class="lang-go hljs"> <span class="token comment">// 可以使用位运算代替模运算的原因,见以下公式:</span> hash <span class="token operator">%</span> <span class="token number">2</span><span class="token operator">^</span>n <span class="token operator">=</span> hash <span class="token operator">&</span> <span class="token punctuation">(</span><span class="token number">2</span><span class="token operator">^</span>n <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">// 5 % 8 = 5 & 7 = 0110 & 0111 = 0110 = 5</span> <span class="token comment">// 13 % 8 = 13 & 7 = 1110 & 0111 = 0110 =5</span> </code></pre>

4、<code>Hashtable</code>中求完hash值,在进行数组寻址时,使用的取模运算

<pre><code class="lang-go hljs"> <span class="token builtin">int</span> index <span class="token operator">=</span> <span class="token punctuation">(</span>hash <span class="token operator">&</span> <span class="token number">0x7FFFFFFF</span><span class="token punctuation">)</span> <span class="token operator">%</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token comment">// 此处hash和0x7FFFFFFF的一次位与操作,是为了保证得到的index值首位为0(代表正数),其实就是在取绝对值。以避免负数计算index的复杂度</span> <span class="token comment">// tab.length为Hashtable的长度。默认初始化为11,之后rehash每次扩容为oldCapacity * 2 1</span> </code></pre> <ul><li>前面说过,HashMap之所以不用取模的原因是为了提高效率,为什么Hashtable还要使用?有人认为,因为HashTable是个线程安全的类,本来就慢,所以Java并没有考虑效率问题,就直接使用取模算法了呢?但是其实并不完全是,Java这样设计还是有一定的考虑在的,虽然这样效率确实是会比HashMap慢一些。</li><li>HashTable简单的取模是有一定的考虑在的。这就要涉及到HashTable的构造函数和扩容函数。Hashtable的长度:默认初始化为11,之后rehash每次扩容为oldCapacity * 2 1。也就是说,HashTable的链表数组的默认大小是一个素数、奇数。之后的每次扩充结果也都是奇数。。</li><li>由于HashTable会尽量使用素数、奇数作为容量的大小。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。这就是文章开头所提到的,问题来源</li></ul>

来自

https://www.jianshu.com/p/e4cbe8c545cf

https://www.cnblogs.com/nima/p/12724872.html
https://www.cnblogs.com/itbsl/p/10600110.html

到此这篇关于“golang之哈希表:散列查找算法”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Python中的哈希表是什么
【golang】HashMap原理和实现
golang之哈希表:散列查找算法
「对比Python学习Go」- 高级数据结构下篇
python hash是什么
数据结构和算法(Golang实现)(28)查找算法-AVL树
golang map源码分析
我们应该如何保护用户的密码
go map实现
Golang map的底层实现

[关闭]
~ ~