数据结构之Hash(java语言版)
Hash表
Hash也叫散列、哈希,是一种根据key-value对进行存储的数据结构。每个value对应一个key,这样查找的时候就无需遍历。
Hash表使用数组作为底层结构,数组中每个区域都存储着Hash,这就是Hash表。
列表、数组、树这些数据结构在查询数据时的时间复杂度通常为O(n),而Hash的时间复杂度为O(1).
Hash函数:哈希表的键值之间必须有个映射关系,这个关系就是hash函数,如key=f(value).Hash的优劣取决于Hash函数。
哈希碰撞
如果不同的value的key一样就会造成hash碰撞。
比如我们选择的Hash函数为取余函数,以10为取余单位,11和1取到的余数是一样的,
如果我们用余数作为key,那就会引发hash碰撞。
解决方法
开放地址法:让相同的key再找一块儿地方待着,比如1的key待在数组的下标1索引的位置上,11的key待在其他位置,比如待在2的位置(前提是2这里没其他人)。
链地址法:在value的地址上加一个链表,一个个连在一起
实例
通过实例实现一下,选取取余函数作为Hash函数,余数充当key,我们使数组的每个下标对应一个key。
如11的余数是1,则11的key就是1,那11就存储在数组中下标为1的位置。
如果出现冲突,使用开放地址法解决Hash冲突。
数据单位:
class DataItem{//数据项
private int iData;
public DataItem(int iData){
this.iData = iData;
}
public int getKey(){
return iData;
}
}
取余函数
取余函数的被余数使用数组长度就好。
//通过哈希函数转换得到数组下标
public int hashFunction(int key){
return key%arraySize;
}
相关结构
数据成员,构造函数以及基础方法。
private DataItem[] hashArray; //DataItem类,表示每个数据项信息
private int arraySize;//数组的初始大小
private int itemNum;//数组实际存储了多少项数据
private DataItem nonItem;//用于删除数据项
public Hash(int arraySize){//构造方法
this.arraySize = arraySize;
hashArray = new DataItem[arraySize];
nonItem = new DataItem(-1);//删除的数据项下标为-1
}
//判断数组是否存储满了
public boolean isFull(){
return (itemNum == arraySize);
}
//判断数组是否为空
public boolean isEmpty(){
return (itemNum == 0);
}
//打印数组内容
public void display(){
System.out.println("data:");
for(int j = 0 ; j < arraySize ; j++){
if(hashArray[j] != null){
System.out.print(hashArray[j].getKey() + " ");
}else{
System.out.print("** ");
}
}
}
删除、查找
//删除数据项
public DataItem delete(int key){
if(isEmpty()){
System.out.println("Hash Table is Empty!");
return null;
}
int hashVal = hashFunction(key);
while(hashArray[hashVal] != null){
if(hashArray[hashVal].getKey() == key){
DataItem temp = hashArray[hashVal];
hashArray[hashVal] = nonItem;//nonItem表示空Item,其key为-1
itemNum--;
return temp;
}
++hashVal;
hashVal %= arraySize;
}
return null;
}
//查找数据项
public DataItem find(int key){
int hashVal = hashFunction(key);
while(hashArray[hashVal] != null){
if(hashArray[hashVal].getKey() == key){
return hashArray[hashVal];
}
++hashVal;
hashVal %= arraySize;
}
return null;
}
插入
//插入数据项
public void insert(DataItem item){
if(isFull()){
//扩展哈希表
System.out.println("哈希表已满,重新哈希化...");
extendHashTable();
}
int key = item.getKey();
int hashVal = hashFunction(key);
while(hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1){
++hashVal;
hashVal %= arraySize;
}
hashArray[hashVal] = item;
itemNum++;
}
扩容
数组有固定的大小,而且不能扩展,所以扩展哈希表只能另外创建一个更大的数组,然后把旧数组中的数据插到新的数组中。
但是哈希表是根据数组大小计算给定数据的位置的,所以这些数据项不能再放在新数组中和老数组相同的位置上。
因此不能直接拷贝,需要按顺序遍历老数组,并使用insert方法向新数组中插入每个数据项。
这个过程叫做重新哈希化。这是一个耗时的过程,但如果数组要进行扩展,这个过程是必须的。
public void extendHashTable(){
int num = arraySize;
itemNum = 0;//重新计数,因为下面要把原来的数据转移到新的扩张的数组中
arraySize *= 2;//数组大小翻倍
DataItem[] oldHashArray = hashArray;
hashArray = new DataItem[arraySize];
for(int i = 0 ; i < num ; i++){
insert(oldHashArray[i]);
}
}
完整实现
public class Hash{
private DataItem[] hashArray; //DataItem类,表示每个数据项信息
private int arraySize;//数组的初始大小
private int itemNum;//数组实际存储了多少项数据
private DataItem nonItem;//用于删除数据项
public Hash(int arraySize){
this.arraySize = arraySize;
hashArray = new DataItem[arraySize];
nonItem = new DataItem(-1);//删除的数据项下标为-1
}
//判断数组是否存储满了
public boolean isFull(){
return (itemNum == arraySize);
}
//判断数组是否为空
public boolean isEmpty(){
return (itemNum == 0);
}
//打印数组内容
public void display(){
System.out.println("data:");
for(int j = 0 ; j < arraySize ; j++){
if(hashArray[j] != null){
System.out.print(hashArray[j].getKey() + " ");
}else{
System.out.print("** ");
}
}
}
//通过哈希函数转换得到数组下标
public int hashFunction(int key){
return key%arraySize;
}
//插入数据项
public void insert(DataItem item){
if(isFull()){
//扩展哈希表
System.out.println("哈希表已满,重新哈希化...");
extendHashTable();
}
int key = item.getKey();
int hashVal = hashFunction(key);
while(hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1){
++hashVal;
hashVal %= arraySize;
}
hashArray[hashVal] = item;
itemNum++;
}
public void extendHashTable(){
int num = arraySize;
itemNum = 0;//重新计数,因为下面要把原来的数据转移到新的扩张的数组中
arraySize *= 2;//数组大小翻倍
DataItem[] oldHashArray = hashArray;
hashArray = new DataItem[arraySize];
for(int i = 0 ; i < num ; i++){
insert(oldHashArray[i]);
}
}
//删除数据项
public DataItem delete(int key){
if(isEmpty()){
System.out.println("Hash Table is Empty!");
return null;
}
int hashVal = hashFunction(key);
while(hashArray[hashVal] != null){
if(hashArray[hashVal].getKey() == key){
DataItem temp = hashArray[hashVal];
hashArray[hashVal] = nonItem;//nonItem表示空Item,其key为-1
itemNum--;
return temp;
}
++hashVal;
hashVal %= arraySize;
}
return null;
}
//查找数据项
public DataItem find(int key){
int hashVal = hashFunction(key);
while(hashArray[hashVal] != null){
if(hashArray[hashVal].getKey() == key){
return hashArray[hashVal];
}
++hashVal;
hashVal %= arraySize;
}
return null;
}
}
测试方法
public static void main(String[] args) {
Hash hash=new Hash(10);
hash.insert(new DataItem(11));
hash.insert(new DataItem(8));
hash.insert(new DataItem(1));
hash.insert(new DataItem(123));
hash.insert(new DataItem(39));
hash.insert(new DataItem(13));
hash.display();
}
结果:
data:
** 11 1 123 13 ** ** ** 8 39