第二个算法作业的设计与实现。做的感觉不是很漂亮,希望大家在看完之后能给一些建议,互相学习。
任务要求:任务要求:实现OPEN 哈希表模板类(不得使用C++或JAVA自有的哈希表类)。用哈希表实现一个英语词典(1000个词,在第三个实验中使用)
算法思想:
哈希表也是一种数据结构,它能提供快速的插入和查找的功能。它是基于数组存储数据,因此,它继承了数组的比较好的一个优点,即能在O(1)时间内定位数据。同时,它也有着数组不具有的另一个优点,即对于插入删除操作,它不需要移动大量数据。而它的缺点也很明显,在数组创建后容量固定,如果数据多了之后,我们需要不断扩充容量。
哈希表的存储结构是D(数据元素集)到M(表地址集)的映射,一般的查找方法都是基于关键字比较进行的,所以导致需要定位一个数据元素的位置时,需要做大量的比较。而哈希表的算法思想是通过一种可以直接计算的方式,对于已给定的数据元素key,直接算出key在表中的位置,这样我们的查找就不必进行关键字的比较。
设计思路:
首先我们需要定义一个符合我们自己实际情况的数据类型,按照题目要求,我们的数据类型要存储字符串类型的单词,这个数据结构也应提供初始化函数以及向外部提供我们存储的数据的方法。然后,我们还要设计一个我们自己的哈希表模板类MyHashTable,类中应该包含hash数组,数组长度,初始化函数,哈希函数,增删查三种操作,以及方便我们打印结果的函数。
程序代码:
自定义的数据类型:
package HashTemplateImpl;
/**
*
*@ClassName DataItem.java
*@author Leno E-mail:cliugeek@us-forever.com
*@date 2017年11月28日下午10:21:03
*@Description 自定义数据类型
*/
public class DataItem {
private Object iData; //关键字
public DataItem(Object initData){ //构造器
this.iData = initData;
}
public Object getKey(){ //获取关键字
return iData;
}
}
哈希表模板类的实现:
package HashTemplateImpl;
/**
*
*@ClassName MyHashMap.java
*@author Leno E-mail:cliugeek@us-forever.com
*@date 2017年11月28日下午9:58:53
*@Description 哈希表模板类实现
*/
public class MyHashTable {
DataItem[] hashArray;
int arraySize;// 定义数组长度
public MyHashTable(int size) {// 构造器,初始化
arraySize = size;
hashArray = new DataItem[arraySize];
}
// 哈希函数
public int hash(String key) {
int h = 0;
for (int i = 0; i < key.length(); i++)
h = 31 * h + key.charAt(i);
return h % arraySize;
}
// 插入,这里假设是数组未满,即不能插入大于arraySize的数据数
public void insert(DataItem item) {
Object key = item.getKey();
String A = key.toString();
int hashCode = hash(A);
// 若已存在同样的数据,则向下进一位,直到找到空的位置
// 为了简单,也可要求不准有重复数据
while (hashArray[hashCode] != null) {
++hashCode;
hashCode %= arraySize;
}
hashArray[hashCode] = item;
}
// 删除
public DataItem delete(DataItem item) {
String key = item.getKey().toString();
int hashCode = hash(key);
while (hashArray[hashCode] != null) {
if (hashArray[hashCode].getKey().toString().equals(key)) {
DataItem temp = hashArray[hashCode];
hashArray[hashCode] = null;
return temp;
}
++hashCode;
hashCode %= arraySize;
}
return null;
}
// 查找
public DataItem find(DataItem item) {
String key = item.getKey().toString();
int hashCode = hash(key);
while (hashArray[hashCode] != null) {
if (hashArray[hashCode].getKey().toString().equals(key))
return hashArray[hashCode];
++hashCode;
hashCode %= arraySize;
}
return null;
}
// 列出全部数据
public void show() {
for (int i = 0; i < arraySize; i++) {
if (hashArray[i] != null)
System.out.print(hashArray[i].getKey() + " ");
else
System.out.print("* ");
}
System.out.println();
}
public static void main(String[] args) {
MyHashTable ht = new MyHashTable(10);
ht.insert(new DataItem("A"));
ht.insert(new DataItem("B"));
ht.insert(new DataItem("C"));
ht.insert(new DataItem("D"));
ht.insert(new DataItem("E"));
ht.show();
DataItem i = ht.find(new DataItem("C"));
System.out.println("i = "+i.getKey());
DataItem di = ht.delete(new DataItem("C"));
System.out.println("di = "+di.getKey());
ht.show();
ht.insert(new DataItem("C"));
ht.show();
ht.insert(new DataItem(1));
ht.show();
DataItem f = ht.find(new DataItem(1));
if(f!=null)
System.out.println(f.getKey());
else
System.out.println("找不见");
DataItem d2 = ht.delete(new DataItem(1));
if(d2!=null)
System.out.println("d2="+d2.getKey());
else
System.out.println("很难受");
ht.show();
}
}
分析:
以上运行结果符合我们的预期,当第二次插入”C”的时候,程序按照依次寻找下一个空位置的方法,找到了元素”D”后边的第一个空位,当需要删除元素”C”的时候,我们根据hash函数找到第一个存放C的位置,并顺利删除掉了该位置上的数据。
该哈希模板适合元素插入不重的情况下使用,如果有相同的元素,例如本例中给的有两个相同的元素插入时,插入操作并不会有任何问题,但是删除操作存在着一定的缺陷,以测试例为例,当成功删除第一个元素C以后,如果再想删除第二个元素C,则会返回null值,在输出打印的时候便会报一个空指针的异常。其实这样的设计也是合理的,如果我们在不知道后续位置是否存在我们想要删除的值的时候,我们需要做遍历整个哈希表的操作,这样会浪费大量的资源,而遍历操作对于我们的哈希表来说,本来就是违背哈希原则的。
哈希表的插入删除查找操作的时间复杂度均为O(1)。
博客修改原因:
群众的眼睛是雪亮的,点名感谢郭嘉琪同学。
昨天提交的代码中,DataItem类中的数据只适合String类型的数据,为了让代码更贴切模板类这个说法,我想到的修改是将DataItem中的数据改为了Object类型。如果初始化一个DataItem对象,我所有的操作都是将这个对象的iData字段转化为String类型,然后根据哈希函数求出的位置,再做相应的操作。
另:
如果大家有兴趣,可以看一下哈希函数中那个for循环,里面的31是有讲究的,大家可以自行学习下选31的原理。
还有这个地方很容易出现数组越界问题,原因就是这个31太大了,经测试,当有5个字母的单词出现的时候很容易就越界了,所以建议大家改的小一点。