数据结构之散列函数

1、散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。提供了快速的插入和查找操作,其基于数组实现。​​其基本思想就是将关键字key均匀映射到散列表下标0~TableSize-1这个范围之内的某个数。

2、散列函数构造方法:

  1>直接定址法:所谓直接定址法就是说,取关键字的某个线性函数值为散列地址,即

        优点:简单、均匀,也不会产生冲突。             缺点:需要事先知道关键字的分布情况,适合查找表较小且连续的情况。

          由于这样的限制,在现实应用中,此方法虽然简单,但却并不常用。

  2>数字分析法:如果关键字时位数较多的数字,比如11位的手机号"130****1234",其中前三位是接入号;中间四位是HLR识别号,表示用户号的归属地;后四为才是真正的用户号。如下图所示。

    如果现在要存储某家公司的登记表,若用手机号作为关键字,极有可能前7位都是相同的,选择后四位成为散列地址就是不错的选择。若容易出现冲突,对抽取出来的数字再进行反转、右环位移等。总的目的就是为了提供一个散列函数,能够合理地将关键字分配到散列表的各个位置。

    数字分析法通过适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布比较均匀,就可以考虑用这个方法。

    3>平方取中法: 这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。

      平方取中法比较适合不知道关键字的分布,而位数又不是很大的情况。

    4>

3、哈希化:(1)直接将关键字作为索引;(2)​将字符串转换成索引:将字符串转换成ascii码,然后进行相加;幂的连乘;压缩可选值。

4、处理散列冲突的方法:

  4.1 开放定址法:所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

  它的公式为:

    比如说,关键字集合为{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},表长为12。散列函数f(key) = key mod 12。

    当计算前5个数{12, 67, 56, 16, 25}时,都是没有冲突的散列地址,直接存入,如下表所示。

    计算key = 37时,发现f(37) = 1,此时就与25所在的位置冲突。于是应用上面的公式f(37) = (f(37) + 1) mod 12 =2,。于是将37存入下标为2的位置。如下表所示。

    接下来22,29,15,47都没有冲突,正常的存入,如下标所示。

    到了48,计算得到f(48) = 0,与12所在的0位置冲突了,不要紧,我们f(48) = (f(48) + 1) mod 12 = 1,此时又与25所在的位置冲突。于是f(48) = (f(48) + 2) mod 12 = 2,还是冲突......一直到f(48) = (f(48) + 6) mod 12 = 6时,才有空位,如下表所示。

 

    把这种解决冲突的开放定址法称为线性探测法

    考虑深一步,如果发生这样的情况,当最后一个key = 34,f(key) = 10,与22所在的位置冲突,可是22后面没有空位置了,反而它的前面有一个空位置,尽管可以不断地求余后得到结果,但效率很差。因此可以改进di=12, -12, 22, -22.........q2, -q2(q<= m/2),这样就等于是可以双向寻找到可能的空位置。对于34来说,取di = -1即可找到空位置了。另外,增加平方运算的目的是为了不让关键字都聚集在某一块区域。称这种方法为二次探测法。

    还有一种方法,在冲突时,对于位移量di采用随机函数计算得到,称之为随机探测法

    既然是随机,那么查找的时候不也随机生成di 吗?如何取得相同的地址呢?这里的随机其实是伪随机数。伪随机数就是说,如果设置随机种子相同,则不断调用随机函数可以生成不会重复的数列,在查找时,用同样的随机种子,它每次得到的数列是想通的,相同的di 当然可以得到相同的散列地址。

    总之,开放定址法只要在散列表未填满时,总是能找到不发生冲突的地址,是常用的解决冲突的方法。

  public void insert(Info info) {

    //获得关键字

    String key = info.getKey();

    //关键字所自定的哈希数

    int hashVal = hashCode(key);

    //如果这个索引已经被占用,而且里面是一个未被删除的数据

    while(arr[hashVal] != null && arr[hashVal].getName() != null) {

      //进行递加

      ++hashVal;

      //循环

      hashVal %= arr.length;

    }

    arr[hashVal] = info;

  }

  public Info find(String key) {

    int hashVal = hashCode(key);

    while(arr[hashVal] != null) {

      if(arr[hashVal].getKey().equals(key)) {

        return arr[hashVal];

      }

      ++hashVal;

      hashVal %= arr.length;

    }

    return null;

  }

  public Info delete(String key) {

    int hashVal = hashCode(key);

    while(arr[hashVal] != null) {

      f(arr[hashVal].getKey().equals(key)) {

        Info tmp = arr[hashVal];

        tmp.setName(null);

        return tmp;

      }

      ++hashVal;

      hashVal %= arr.length;

    }

    return null;

  }

  4.2 再散列函数法:

     对于散列表来说,可以事先准备多个散列函数。

    这里RH就是不同的散列函数,可以把前面说的除留余数、折叠、平方取中全部用上。每当发生散列地址冲突时,就换一个散列函数计算。

    这种方法能够使得关键字不产生聚集,但相应地也增加了计算的时间。

  4.3 链地址法:

  将所有关键字为同义词的记录存储在一个单链表中,称这种表为同义词子表,在散列表中只存储所有同义词子表前面的指针。对于关键字集合{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},用前面同样的12为余数,进行除留余数法,可以得到下图结构。

 

 

    此时,已经不存在什么冲突换地址的问题,无论有多少个冲突,都只是在当前位置给单链表增加结点的问题。

    链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保证。当然,这也就带来了查找时需要遍历单链表的性能损耗。


总结:开放地址法将所有结点均存放在散列表(Hash)T[0..m-1]中,链址法将互为同义词的结点链成一个但链表,而将此链表的头指针放在散列表T[0..m-1]中。与开放地址相比  链地址法有如下优点:1,链地址法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短。2,链地址法中链表的结点是动态申请的,故它更适合造表前无法确定表长的情况,3,开放定址法为了减少冲突要求填充因子较小,故结点规模较大时会浪费很多空间,而链地址法中填充因子可以大于1且结点较大时,拉链法中增加的指针域可以忽略不计,因此节省空间,4,链地址法构造的散列表删除结点很方便,只需简单的删去链表上相应的结点即可。

     public class HashTable {

    private LinkList[] arr;

    public HashTable() {

      arr = new LinkList[100];

    }

    public HashTable(int maxSize) {

      arr = new LinkList[maxSize];

    }

    public void insert(Info info) {

      //获得关键字

      String key = info.getKey();

      //关键字所自定的哈希数

      int hashVal = hashCode(key);

      if(arr[hashVal] == null) {

        arr[hashVal] = new LinkList();

      }

      arr[hashVal].insertFirst(info);

    }

    public Info find(String key) {

      int hashVal = hashCode(key);

      return arr[hashVal].find(key).info;

    }

    public Info delete(String key) {

      int hashVal = hashCode(key);

      return arr[hashVal].delete(key).info;

    }

    public int hashCode(String key) {

      //int hashVal = 0;

      //for(int i = key.length() - 1; i >= 0; i--) {

        //int letter = key.charAt(i) - 96;

        //hashVal += letter;

      //}

      //return hashVal;

      BigInteger hashVal = new BigInteger("0");

      BigInteger pow27 = new BigInteger("1");

      for(int i = key.length() - 1; i >= 0; i--) {

        int letter = key.charAt(i) - 96;

        BigInteger letterB = new BigInteger(String.valueOf(letter));

        hashVal = hashVal.add(letterB.multiply(pow27));

        pow27 = pow27.multiply(new BigInteger(String.valueOf(27)));

      }

      return hashVal.mod(new BigInteger(String.valueOf(arr.length))).intValue();

    }

  }

  public class LinkList {

    //头结点

    private Node first;

    public LinkList() {

      first = null;

    }

    public void insertFirst(Info info) {

      Node node = new Node(info);

      node.next = first;

      first = node;

    }

    public Node deleteFirst() {

      Node tmp = first;

      first = tmp.next;

      return tmp;

    }

    public Node find(String key) {

      Node current = first;

      while(!key.equals(current.info.getKey())) {

        if(current.next == null) {

        return null;

      }

      current = current.next;

    }

    return current;

  }

  public Node delete(String key) {

    Node current = first;

    Node previous = first;

    while(!key.equals(current.info.getKey())) {

      if(current.next == null) {

        return null;

      }

      previous = current;

      current = current.next;

    }

     if(current == first) {

      first = first.next;

     } else {

        previous.next = current.next;

      }

    return current;

  }

}

  public class Node {

    //数据域

    public Info info;

    //指针域

    public Node next;

    public Node(Info info) {

      this.info = info;

    }

  }

   参考链接:http://blog.chinaunix.net/uid-26548237-id-3480645.html

posted @ 2016-05-14 00:54  独孤求败呢  阅读(3772)  评论(0编辑  收藏  举报