zoukankan      html  css  js  c++  java
  • 数据结构之散列表

    前言:

       对于Key-Value集合,想象两种极端的情况:如果没有内存的限制,我们可以直接将键作为一个数组的索引,那么所有的查找只需一次就可以完成;另一方面,如果没有时间的限制,那么我们就可以使用无序的数组进行顺序查找,这样只需要很少的内存。然而这两种情况几乎见不到,作为在时间和空间上做出权衡的经典方法,使用散列表可以在大部分情况下实现常数级别的查找和插入操作!

    一,散列函数

         我们需要有一个将Key转化为数组索引的方法,这个方法就是散列函数。如果我们有一个能保存M个键值对的数组,那么这个散列函数就能够将任意Key转化为【0,M-1】之间的索引。

    散列函数和键的类型有关:严格来说:每一种类型的键都有一个对应的散列函数!

    Key的常见情况:

    正整数:

      常用的方法是除留取余法,选择一个大小为素数M的数组,对于任意正整数K,计算K%M,选择素数的原因是使用素数,散列值的分布会更好!

    浮点数:

    如果键是0到1之间的小数,可以将他乘以M并四舍五入得到一个【0,M】之间的值,

    但是有很大的缺陷,这种情况下键的高位起的作用更大,而最低为几乎没有影响!

    比如0.554433*100=55;显然,4433这四位不起任何作用。而在Java里,对这种情况的处理是将键转化为二进制然后再使用除留取余法。

    字符串:

    除留取余法也可以处理字符串,只要我们将他们当做大整数处理即可。

    例如:计算String的哈希值。

        

    1   int hash=0;
    2 
    3            for(int i=0;i<s.length();i++)
    4 
    5            {
    6 
    7                   hash=(R*hash+s.charAt(i))%M;
    8 
    9            }

    charAt()返回一个char,即非负16位整数,如果R足够小,不造成溢出,那摩结果就能落在【0,M】之间。事实上,Java中的String的hashCode()函数就是使用类似的 方法实现的。源码如下:

         

     1 public int hashCode() {
     2 
     3         int h = hash;
     4 
     5         if (h == 0 && value.length > 0) {
     6 
     7             char val[] = value;
     8 
     9  
    10 
    11             for (int i = 0; i < value.length; i++) {
    12 
    13                 h = 31 * h + val[i];
    14 
    15             }
    16 
    17             hash = h;
    18 
    19         }
    20 
    21         return h;
    22 
    23     }

    组合键:

     对于组合键的处理,我们可以像处理String那样将他们混合起来,如果键的类型为Date,那么可以采用下列方法:

    int hash=(((day*R+month)%M)*R+year)%M;

    //R同String中的R

    Java中的约定:

     Java要求每种类型都需要相应的散列函数,所以他们都继承了一个能返回32比特整数的hashCode()方法。但是每一种数据类型的hashCode()方法都需要和equals()方法一致,即如果a.equals(b)==true;那么一定会有a.hashCode()==b.hashCode();反之则不然,因为这里存在着哈希碰撞的问题,因此,如果你要自己实现hashCode(),请不要忘记实现equals();

    但是因为hashCode默认为32位比特,在实际使用中,我们可以使用除留取余法将他转化为一个合理的整数。例如:

    private int hash(K key)

            {

               return (key.hashCode()&0x7fffffff)%M;

      }//这段代码会屏蔽符号位,将其转化为31位非负整数。

    自定义hashCode

       其实现在的编译器,如eclipse,IDEA,vscode(需使用插件)等都有自动生成hashCode()的功能,很多时候只需要点两下鼠标就行,如果你真想自己写哈希函数,请记住以下三个原则:

       一致性——等价的键必定产生相等的哈希值

     高效性——计算简便

    均匀性——均匀散列所有键

    基于拉链法的散列表

    Java 标准库的 HashMap 基本上就是用 拉链法 实现的。 拉链法 的实现比较简单,将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

     

    我们需要使用M条链表来保存N个键,无论键的分布如何,链表的平均长度肯定为N/M;具体实现代码如下:

      定义链表,采用顺序搜索:

      

     public class SequentialSearch<K,V>{
    
      private Node first;
    
      
    
       private class Node
    
       {
    
          K key;
    
          V value;
    
          Node next;
    
         
    
          public Node(K k,V v,Node next)
    
          {
    
             this.key=k;
    
             this.value=v;
    
             this.next=next;
    
            
    
          }
    
       }
    
      
    
       public V get(K key)
    
       {
    
          for(Node x=first;x!=null;x=x.next)
    
             if(key.equals(x.key))
    
                 return x.value;
    
          return null;
    
       }
    
      
    
       public void put(K key,V value)
    
       {
    
          for(Node x=first;x!=null;x=x.next)
    
             if(key.equals(x.key))
    
             {
    
                x.value=value;
    
                return ;
    
             }
    
          first=new Node(key,value,first);
    
       }
    
    }
    View Code

    基于拉链法的散列表:

     

     1 public class ChainHash<K,V> {
     2 
     3   
     4 
     5   private int N;
     6 
     7   private int M;
     8 
     9   private SequentialSearch<K,V>[] array;
    10 
    11  
    12 
    13   public ChainHash()
    14 
    15   {
    16 
    17     
    18 
    19   }
    20 
    21   @SuppressWarnings("unchecked")
    22 
    23   public ChainHash(int size)
    24 
    25   {
    26 
    27      this.M=size;
    28 
    29      array= (SequentialSearch<K,V>[])new SequentialSearch[M];
    30 
    31 //Java不允许创建泛型数组,需要强制转换
    32 
    33      for(int i=0;i<M;i++)
    34 
    35      {
    36 
    37         array[i]=new SequentialSearch();
    38 
    39      }
    40 
    41   }
    42 
    43  
    44 
    45   private int hash(K key)
    46 
    47   {
    48 
    49      return (key.hashCode()&0x7fffffff)%M;
    50 
    51   }
    52 
    53  
    54 
    55   public V get(K key)
    56 
    57   {
    58 
    59     return  (V)array[hash(key)].get(key);
    60 
    61   }
    62 
    63  
    64 
    65   public void put(K key,V value)
    66 
    67   {
    68 
    69        array[hash(key)].put(key, value);
    70 
    71   }
    72 
    73 }
    74 
    75  
    View Code

    线性探测法

       用大小为M的数组保存N个键值对

    通过散列函数hash(key),找到关键字key在线性序列中的位置,如果当前位置已经有了一个关键字,就长生了哈希冲突,我们直接检查散列表的下一位置, 此时会产生三种结果:

    • *命中:该位置的键和被查找的键相同(更新value)
    • *未命中:该位置没有键(插入相关Key-Value)
    • *继续查找:该位置的键和被查找的键不同

     

    代码实现:

        这里使用了并行数组,一条保存键,一条保存值

         

      1 public class LineHash<K,V> {
      2 
      3  
      4 
      5    private int N;
      6 
      7    private int M=16;
      8 
      9    private K[] keys;
     10 
     11    private V[] values;
     12 
     13   
     14 
     15    public LineHash()
     16 
     17    {
     18 
     19       keys=(K[])new Object[M];
     20 
     21       values=(V[]) new Object[M];
     22 
     23    }
     24 
     25   
     26 
     27    private int hash(K key)
     28 
     29    {
     30 
     31       return (key.hashCode()&0x7fffffff)%M;
     32 
     33    }
     34 
     35    private void resize()
     36 
     37    {
     38 
     39       //TODO
     40 
     41    }
     42 
     43   
     44 
     45    public void put(K key,V value)
     46 
     47    {
     48 
     49       int i;
     50 
     51       for(i=hash(key);keys[i]!=null;i=(i+1)%M)
     52 
     53       {
     54 
     55          if(keys[i].equals(key))
     56 
     57          {
     58 
     59             values[i]=value;
     60 
     61             return;
     62 
     63          }
     64 
     65       }
     66 
     67      
     68 
     69       keys[i]=key;
     70 
     71       values[i]=value;
     72 
     73       N++;
     74 
     75    }
     76 
     77   
     78 
     79    public V get(K key)
     80 
     81    {
     82 
     83       int i;
     84 
     85       for(i=hash(key);keys[i]!=null;i=(i+1)%M)
     86 
     87       {
     88 
     89          if(keys[i].equals(key))
     90 
     91             return values[i];
     92 
     93       }
     94 
     95       return null;
     96 
     97    }
     98 
     99   
    100 
    101    @Override
    102 
    103    public String toString()
    104 
    105    {
    106 
    107       StringBuilder sb=new StringBuilder();
    108 
    109       for(int i=0;i<M;i++)
    110 
    111       {
    112 
    113          sb.append(i+"  ").append(keys[i]).append("= ").append(values[i]).append("
    ");
    114 
    115       }
    116 
    117       return sb.toString();
    118 
    119    }
    120 
    121   
    122 
    123 }
    View Code
  • 相关阅读:
    redis set
    实现排行榜神器——redis zset
    nginx挂了怎么办
    django 400报错
    项目管理【18】 | 项目进度管理-规划进度管理
    项目管理【17】 | 项目进度管理-进度管理概述
    项目管理【23】 | 项目进度管理-制定进度计划
    移动端开发基础【7】生命周期
    移动端开发案例【7】移动端朋友圈发布显示开发
    移动端开发案例【6】移动端群、用户搜索开发
  • 原文地址:https://www.cnblogs.com/lls101/p/11128992.html
Copyright © 2011-2022 走看看