之前的博文中提到了使用拉链法来实现hashmap,本以为掌握了手撕“拉链法”的哈希表已经不错了,没想到今天遇到了要求:使用开放地址法实现哈希表。虽然说最后还是写出来了,但是当时写的代码实在是“又臭又长”,很多地方不够精简,虽然也许能够实现相同的功能,但是可读取比较低,因为完全可以使用更加优雅,更加简练的代码完成。因此重新思考了一下写下此博客,作为一个总结。
首先本题要求的开放地址很简单,就是如果出现了哈希冲突,那就看下一位行不行,如果不行,继续往下,并且本题不要求写出resize和rehash方法。就假设hashmap是定长的,如果超出了长度要求就直接抛出异常。
我之前写的代码存在的问题:
1、因为要求出现哈希冲突就不断向后找,因此我是首先一个while循环,范围是小于hashmap的长度,在这个里面搜索有没有空的,空的直接放入,如果超出长度,再从下标为0的元素开始搜索。因此除了刚才的while循环,还写了一个if -else来判断到底是找到了,还是超出数组下标了,如果超出下标,那么还要再添加一个while用来查找从0位置开始的元素有没有空的。
可以看出来,真的是非常复杂,其实用更简单的逻辑也可以实现相同的功能,那就是取余%。。。。因为一旦超出下标,使用取余数自然就会从0开始重新算,根本不需要之前那么繁琐。
2、删除元素的时候需要考虑一种特殊情况,那就是比如一开始添加key=“你好”的元素,发现此时下标2被占了,往下搜索3为null,因此放在下标为3的位置,后面又将下标为2的元素删除了,然后我们又准备查找key为“你好”的元素,他计算出来的下标为2,那么问题来了,这个key为“你好”的元素此时到底是已经删除了还是没有删除呢?
我当时的想法是在每个元素里面添加一个flag标志,表示这个元素有没有被移动过,也就是添加的时候有没有出现哈希冲突导致重选了地址。但是其实还有更简单的方法。那就是每次删除的时候把这个位置的元素赋值为一个“空对象”,空对象不是指null,而是一个特殊的对象,比如他的key为-1,或者让他的key是null,但是这个元素不是null。
代码如下,写的匆忙,如果有问题可以提出来。
/**
* 哈希表,解决冲突的方法为:开放地址法,线性探测,探测步长为:1
*/
public class HashTable {
private DataItem[] hashArray; //哈希表底层的数组
private int tableSize; //哈希表的大小,即为数组容量
private DataItem nonItem; //删除的时候自己定义的一个空对象,代表删除标志位,查找的时候会跳过这个标志位
//构造方法,初始化变量
public HashTable(int size) {
tableSize = size;
hashArray = new DataItem[tableSize];
nonItem = new DataItem(-1);
}
//哈希表的打印方法
public void displayTable() {
System.out.print("Table: ");
for (int i = 0; i < tableSize; i++) {
if (hashArray[i] != null) {
System.out.printf(hashArray[i].getData() + " ");
} else {
System.out.printf("** ");
}
}
System.out.println();
}
//哈希函数:哈希表的灵魂,这里采用取模法
public int hashFunc(int key) {
return key % tableSize;
}
//插入哈希表的方法
public void insert(DataItem item) {
int key = item.getData();
int hashVal = hashFunc(key); //对插入哈希表的数据求哈希值
//当遇到冲突的时候进行线性探测,即哈希值自加一,探测步长为:1
while (hashArray[hashVal] != null && hashArray[hashVal].getData() != -1) {
++hashVal;
hashVal %= tableSize; //这里对表大小取模是防止哈希值超过表的大小,也可以判断之后归零
}
hashArray[hashVal] = item; //将数据插入到合适的位置
}
//移除操作,传入关键值
public DataItem delete(int key) {
int hashVal = hashFunc(key); //计算关键值的哈希值
//如果第一次没找到说明发生了冲突,需要线性探测
while (hashArray[hashVal] != null) {
if (hashArray[hashVal].getData() == key) {
DataItem temp = hashArray[hashVal];
hashArray[hashVal] = nonItem;
return temp;
}
++hashVal;
hashVal %= tableSize;
}
return null;
}
//查找方法,其实插入和删除中都包含查找方法,只不过各有不同
public DataItem find(int key) {
int hashVal = hashFunc(key);
while (hashArray[hashVal] != null) {
if (hashArray[hashVal].getData() == key) {
return hashArray[hashVal];
}
++hashVal;
hashVal %= tableSize;
}
return null;
}
}