哈希表作为一种数据结构,在百度百科上的定义为:散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。当时不理解这个概念是什么意思,查询了一些资料,可以简单地理解为:定义了一个表(广义上的储存数据的数据结构),根据自己定义的存放数据的函数,把这些数据放到各自的位置上。
根据这个定义,就能解释定义中“直接访问”的含义,因为自己定义的函数,查询的时间复杂度几乎为O(1),这里说几乎是因为有些程序员可能根据情况的不同定义一些较为复杂的函数。但是相比于树,查询效率就大了很多。
所以在这里,要建立一个哈希表,得有两个东西,第一个是哈希函数,用于存取数据的位置,第二个是储存数据的数据结构。
在这里,因为“直接访问”这个特性的存在,所以在数据结构中只能选择顺序结构,于是就剩下数组和链表了。所以缺点也显而易见,第一是有可能数组或者链表的空的位置太多了,浪费资源,第二是在增大数组或者链表时较为麻烦。
以下是比较常见的哈希函数(散列函数):
关键字
|
内部编码
|
内部编码的平方值
|
H(k)关键字的哈希地址
|
KEYA
|
11050201
|
122157778355001
|
778
|
KYAB
|
11250102
|
126564795010404
|
795
|
AKEY
|
01110525
|
001233265775625
|
265
|
BKEY
|
02110525
|
004454315775625
|
315
|
然而还有一个比较重要的东西,在放数据的时候,若是根据函数都放到了一个地方了怎么办?这就是处理冲突的方法,当然如果你自己定义的函数足够好,在数据量足够大的情况下还能保证每个都能分配到唯一的位置,那也是可以的,当然这在现实操作中几乎不可能办到。
以下是常见的处理冲突的方法:
好了,优缺点都说了,下面就放上我比较简易的哈希表建立过程。
第一种是以数组为数据结构,函数为除留余数法,处理冲突的方法为开放寻址法。
哈希函数:
public int HashFunc(T t){//表长求余哈希函数
int pos;
pos=t.hashCode()%this.length;
return Math.abs(pos);
}
哈希表构造方法:
public HashTable(T[] table){//构造函数
int i=0;
int length=table.length;
this.table=(T[])new Object[this.length];
while(i<length){
T t=table[i];
int b=HashFunc(t);
if(this.di[b]!=0){
int j=1;
while(IsEmpty(di[b])){
b=(b+j)%this.length;
}
}
this.table[b]=table[i];
di[b]++;
i++;
}
}
private int[] di=new int[20];
private T[] table;
这里我定义了一个数组,然后一个是增量序列。
接下来是另外一种方式实现哈希表。
数据结构为链表,函数为字符长度,处理冲突的方法为拉链法。
申请了两个内部类。
<span style="white-space:pre"> </span> class HeadNode<T> {
<span style="white-space:pre"> </span> public Node next;
<span style="white-space:pre"> </span> public HeadNode(){
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>this.next=null;
<span style="white-space:pre"> </span> }
<span style="white-space:pre"> </span>
}
<span style="white-space:pre"> </span> class Node<T> {
<span style="white-space:pre"> </span>public Node(){
<span style="white-space:pre"> </span>this.data=null;
<span style="white-space:pre"> </span>this.next=null;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>public T data;
<span style="white-space:pre"> </span>public Node<T> next;
}
private HeadNode[] head=new HeadNode[20];//申请20个头结点
构造函数:
public HashLink(T t[]){
for(int j=0;j<20;j++)
{head[j]=new HeadNode<T>();}
int i=0;
Node<T> node1;
while(i<t.length){
Node<T> node=new Node<T>();
int c=HashFunc(t[i]);
node.data=t[i];
if(head[c].next==null) head[c].next=node; //如果头结点的NEXT为空的话,直接将NODE作为头结点的NEXT
else{ //否则就循环一直到结点为NULL后接上NODE结点
node1=head[c].next;
while(node1.next!=null)node1=node1.next;
node1.next=node;
}
i++;
}
}
哈希函数:
public int HashFunc(T t){//以字符长度座位哈希函数
int pos;
pos=t.toString().length()%this.length;
return Math.abs(pos);
}
输出函数:
public void Output(){
Node<T> node =new Node<T>();
for(int i=0;i<20;i++){
if(head[i].next!=null) {node=head[i].next;
while(node!=null){
System.out.print(node.data+" ");
node=node.next;
}
System.out.println();
}
}
}
插入函数:
public void Insert(T t){
Node<T> node=new Node<T>();
Node<T> node1;
int c=HashFunc(t);
node.data=t;
if(head[c].next==null) head[c].next=node;
else{
node1=head[c].next;
while(node1.next!=null)node1=node1.next;
node1.next=node;
}
}
获取位置函数:
public int GetPosition(T t){
int c=HashFunc(t);
Node<T> node =new Node<T>();
if(head[c].next!=null){
node=head[c].next;
while(node!=null){
if(node.data.equals(t))return c;
node=node.next;
}
}
return -1;
}
主函数测试:
public class Mainn {
public static void main(String[] args){
String str[]={"int","String","youf","float","duetous","cao"};
HashLink<String> h=new HashLink<String>(str);
h.Output();
System.out.println(h.GetPosition("cao"));
System.out.println(h.GetPosition("weaa"));
}
}
结果:
反思:写链表的时候出现了不少问题。
第一个是在定义内部类的时候,我定义的类是public class HashLink<T> ,因为有T这个不定类型的存在,在内部类的时候也得加上这个不然在后面会出错。
第二个是我在调试的过程中总是出现nullpointerexception 这个异常,主要是因为我在创建的时候,不是说不能赋予NULL值或者以NULL值来判断,是因为就根本不存在某个结点,才会出现这种情况。比如说在head判断的时候 只能判断head.next为不为null值,不能判断head为不为null值,因为当时申请了head结点的空间所以不可能为NULL值。这里的nullpointerexception 和NULL值没有关系,和存在不存在有关系。
在输出函数中,判断是用node!=null,这里可以用的原因是因为,在不断地node=node.next;以后,会出现没有申请空间的结点。而之前如果用Head来判断的话,那么在后面,node1=head[c].next;就会出现问题,这里还不会出现问题,主要在node1.next!=null 因为node1已经是NULL了,没有申请空间,也就没有next这一选项