当前位置:   article > 正文

Java哈希表详解

java哈希表

一.什么是哈希表

哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器等。

二.为什么引入哈希表

        直接原因:

顺序结构以及平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素时,必须要经过关键 码的多次比较 顺序查找时间复杂度为 O(N) ,平衡树中为树的高度,即 O(log2N )    ,搜索的效率取决于搜索过程中元素的比较次数。
    所以需要一个查找效率比较快的结构
如果有一种结构 不需要数据的比较就能够找到搜索的元素,那么这种结构就是我们理想的结构,它的查找的时间复杂度为O(1) 
如果构造一种存储结构,通过某种函 (hashFunc) 使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快 找到该元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即为哈希 ( 散列 ) 方法, 哈希方法中使用的转换函数称为 哈希(散列)函数 ,构造出来的结构称为哈希表 (Hash Table)( 或者称散列表 )

三. 哈希函数

例如:数据集合 {1 7 6 4 5 9}

如果哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

解释 此数组的capacity为10   放入1的时候 1%10 = 1 所以放到数组 1 下表

        其他的也类似

 问题:如果我要放入 14 的时候 14 % 10 = 4  可是目前数组4下表已经有元素了 所以14就放不进去

四.哈希冲突

对于两个数据元素的关键字 和 (i != j) ,有 != ,但有: Hash( ) == Hash( ) ,即: 不同关键字通过相同哈希哈数计算出相同的哈希地址 ,该种现象称为哈希冲突或哈希碰撞
把具有不同关键码而具有相同哈希地址的数据元素称为 同义词”。
在上图中 不同的关键字4 和 14 通过相同的哈希函数hash(key) = key % capacity,计算出了相同的值。

那么如何避免哈希冲突?----->设计合理的哈希函数 

冲突的-避免-负载因子调节(重点掌握)

五.精讲    冲突-解决-开散列/哈希桶(重点掌握) 

闭散列先不讲

开散列法又叫链地址法 ( 开链法 ) ,首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个 单链表 链接起来,各链表的头结点存储在哈希表中。 我们可以看到 数组中每一个有效元素的下方都是一个单链表 此 时我们再尝试放入14
就解我决了14放不进去的问题

 如果哈希表中插入重复的数据会怎么样?当插入重复的数据时 会进行覆盖,保证数据唯一性

代码实现

  1. public class hashBucket {
  2. static class Node {
  3. private int key;
  4. private int val;
  5. Node next;
  6. public Node(int key, int val) {
  7. this.key = key;
  8. this.val = val;
  9. }
  10. }
  11. public Node[] array;
  12. public int usedSize;
  13. public static final double LOAD_FACTOR = 0.75;
  14. hashBucket() {
  15. array = new Node[10];
  16. }
  17. public void put(int key, int val) {
  18. /*
  19. 放入元素的时时候 如果有这个数据就覆盖 没有的话进行头插
  20. */
  21. int index = key % array.length; //数组映射关系函数
  22. Node cur = array[index]; //cur指向当前节点,因为要在当前的桶放入元素
  23. while (cur != null) {
  24. if(cur.key == key) { //如果有当前key 说明要进行数据覆盖
  25. cur.val = val;
  26. }
  27. cur = cur.next;
  28. }
  29. //走到这里说明都没有找到重复的值,此时进行头插
  30. Node node = new Node(key,val);
  31. node.next = array[index]; //可以吧array[index]看作为head头结点来进行理解
  32. array[index] = node;
  33. usedSize++;
  34. //查看是否超过了负载因子 如果超过考虑扩容
  35. if(LOAD_FACTOR <= loadFactor()) {
  36. resize();
  37. }
  38. }
  39. public double loadFactor() {
  40. //查看是否超过了负载因子
  41. return usedSize*1.0 / array.length;
  42. }

5.1 哈希扩容

如果当前数组大小不满足需求的时候,需要对数组进行扩容。

需要注意的是,如果进行扩容 那么函数的映射关系会发生改变,也就是说要再新数组中重新映射元素。

比如 数组长度为10  此时14放到了 4 下标
     扩容后数组长度为20  此时14 放到了 14 下标

代码如下

  1. private void resize() {
  2. /*
  3. 进行开散列扩容的时候 需要注意到可能 原有的映射关系可能不对应新的数组
  4. 比如 数组长度为10 此时14放到了 4 下标
  5. 数组长度为20 此时14 放到了 14 下标
  6. 所以要重新进行函数映射关系
  7. */
  8. Node[] newArray = new Node[array.length * 2]; //新数组 2倍扩容
  9. //重新进行映射
  10. for (int i = 0; i < array.length; i++) {
  11. Node cur = array[i]; //从旧数组的0下标开始挨个映射
  12. //在新数组中进行头插
  13. while (cur != null) {
  14. Node curNext = cur.next;
  15. int index = cur.key % newArray.length; //1.找到旧数组下标在新数组当中的位置
  16. cur.next = newArray[index]; //2.头插+
  17. newArray[index] = cur;
  18. cur = curNext;
  19. }
  20. }
  21. array = newArray;
  22. }

5.2获取元素 

可以通过哈希函数设置元素 也可以通过哈希函数获取元素

步骤 1.先找到数组下标

        2.在此下标中链式查找,如果找到就返回

  1. public int get(int key) {
  2. int index = key % array.length; //比如要找18 18在8下标的位置,肯定是链式存储
  3. Node cur = array[index];
  4. while (cur != null) {
  5. if(cur.key == key) {
  6. return cur.val;
  7. }
  8. cur = cur.next;
  9. }
  10. return -1;
  11. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/775680
推荐阅读
相关标签
  

闽ICP备14008679号