数据结构之散列函数

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

时间: 2024-10-12 20:16:44

数据结构之散列函数的相关文章

散列表查找的一个实例

这里解决冲突的方法是开放地址法:“开放地址指的是表中尚未被占用的地址,开放地址法就是当冲突发生时候,形成一个地址序列,沿着这个序列逐个进行探测,直到找到一个空的开放地址,将发生冲突的关键字存放到该地址中去,即Hi=(H(key)+di)%m,i=1,2,..k(k<=m),其中H(key)为散列函数,m为散列表长,di为增量序列. 例题:选取散列函数H(K)=(3K)%11,用开放地址处理冲突,d1=H(K);di=(di+(7K)%10+1)%11(i=2,3,..),试着在HT[0,..10

数据类型和常用数据结构

1.数据类型 几乎是所有的程序设计语言都会讲到数据类型的概念.简单的说,数据类型就是一个值的集合及在这些值上定义的一系列操作的总称.例如:对于C语言的整数类型,其有一定的取值范围,对于整数类型还定义了加法.减法.乘法.除法和取模运算等操作. 按照数据类型的值是否可以分解,数据类型可以分为基本数据类型和聚合数据类型. *基本数据类型:其值不能进一步分解,一般是程序设计语言自身定义的一些数据类型,例如C语言中.字符型.浮点型等. *聚合数据类型:其值可以进一步分解为若干分量,一般是用户自定义的数据类

8. 蛤蟆的数据结构进阶八哈希表相关概念

8. 蛤蟆的数据结构进阶八哈希表相关概念 本篇名言:"作家当然必须挣钱才能生活,写作,但是他决不应该为了挣钱而生活,写作.--马克思" 前些笔记我们学习了二叉树相关.现在我们来看下哈希表.这篇先来看下哈希表的相关概念 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/47347273 1.  哈希表的概念 哈希表(HashTable)也叫散列表,是根据关键码值(Key Value)而直接进行访问的数据结构.它通过把关键

数据结构-List接口-LinkedList类-Set接口-HashSet类-Collection总结

一.数据结构:4种--<需补充> 1.堆栈结构:     特点:LIFO(后进先出);栈的入口/出口都在顶端位置;压栈就是存元素/弹栈就是取元素;     代表类:Stack;     其它:main方法最后一个出去; 2.数组结构:     特点:一片连续的空间;有索引,查找快;增删慢;     代表类:ArrayList;     应用场景:用于查询多的场景,如天气预报; 3.队列结构:     特点:FIFO(先进先出);入口/出口在两侧;     代表:Queue接口     应用场景

数据结构与算法笔记(1)基本概念

1.什么是数据结构 用计算机解决一个具体的问题,需要以下几个步骤: 从具体问题抽象出一个适当的数学模型: 设计一个解此数学模型的算法: 编出程序: 进行测试.调整直至得到最终解答. 寻求数学模型的实质: 分析问题,从中提取操作的对象,并找出这些操作对象之间含有的关系,然后用数学的语言加以描述. 很多问题求解最后都转化为求解数学方程或数学方程组.如:求解梁架结构中应力的数学模型为线性方程组.预报人口增长情况的数学模型为微分方程.然而:当计算机进入非数值计算领域,特别是用在管理上的时候,计算机的操作

数据结构基础知识1

一. 理解并能快速.准确写出代码.(★★★★★) 1. 几种常见排序( 代码 ) 基于比较的排序算法: 下界是 nlgn 1.1 SelectionSort:每次选出最下的元素,放在当前循环最左边的位置. 1.2 BubbleSort:每次比较相邻的两个数,使得最大的数像气泡一样冒到最右边. 1. 3 InsertionSort:每次拿起一个数,插入到它左边数组的正确位置. 1.4 QuickSort:选择一个数,作为标准,小于它的放在左边,大于它的放在右边.并把它放在中间:递归地对左右子数组进

数据结构(DataStructure)与算法(Algorithm)、STL应用

catalogue 0. 引论 1. 数据结构的概念 2. 逻辑结构实例 2.1 堆栈 2.2 队列 2.3 树形结构 2.3.1 二叉树 3. 物理结构实例 3.1 链表 3.1.1 单向线性链表 3.1.2 单向循环链表 3.1.3 双向线性链表 3.1.4 双向循环链表 3.1.5 数组链表 3.1.6 链表数组 3.1.7 二维链表 3.2 顺序存储 4. 算法 4.1 查找算法 4.2 排序算法 0. 引论 0x1: 为什么要学习数据结构 N.沃思(Niklaus  Wirth)教授提

数据结构(Java语言)——HashTable(开放定址法)简单实现

分离链接散列算法的缺点是使用一些链表.由于给新单元分配地址需要时间,因此这就导致算法的速度有些减慢,同时算法实际上还要求对第二种数据结构的实现.另有一种不用链表解决冲突的方法是尝试另外一些单元,直到找出空的单元为止.更常见的是,单元h0(x),h1(x),h2(x),...相继被试选,其中hi(x)=(hash(x)+f(i)) mod TableSize,且f(0)=0.函数f是冲突解决方法,因为所有的数据都要置于表内,所以这种解决方案所需要的表要比分离链接散列的表大.一般来说,对于不使用分离

java的动态数据结构和泛型

动态数据结构和泛型 0 详细介绍java中的数据结构 1 1 List 5 1.1 ArrayList 5 2 Set 6 2.1 HashSet与TreeSet的区别 6 3 Map 8 4 迭代器 9 5 泛型 9 0 详细介绍java中的数据结构 也许你已经熟练使用了java.util包里面的各种数据结构,但是我还是要说一说java版数据结构与算法,希望对你有帮助. 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些类