哈希表(二)
链地址法
开放地址法中,通过哈希表中寻找一个空白单元解决冲突问题。另一个方法是在哈希表的每个单元中设置一个链表,某个数据项的关键字还是像通常一样映射到哈希表的单元中,而数据项本身插入到这个单元的链表中。不需要在哈希表中寻找空白单元。
链地址法的装填因子与开放地址法不同,在链地址法中,需要在有N个单元数组中装入N或更多的数据项;因此,装填因子一般为1,或大于1,因为某些位置的链表含有两个或两个以上数据项。
在开放地址中,装填因子在超过二分之一或者三分之二后,性能下降很快。在链地址法中,装填因子可以达到1以上,而且对性能影响不大,因此,链地址法是更健壮的机制。
public class Link { private int i; private Link next; public Link(int i) { this.i = i; } public int getKey() { return i; } public Link getNext() { return next; } public void setNext(Link next) { this.next = next; } public void printf() { System.out.println("data -> " + i); } }
public class SortedList { private Link first; public boolean isEmpty() { return first == null; } public void insert(Link link) { int key = link.getKey(); Link previous = null; Link current = first; while (current != null && key > current.getKey()) { previous = current; current = current.getNext(); } if (previous == null) { first = link; } else { previous.setNext(link); } link.setNext(current); } public Link delete(int key) { Link current = first; Link privious = null; if (!isEmpty()) { while (current != null && current.getKey() != key) { privious = current; current = current.getNext(); } if (privious == null) { first = first.getNext(); } else { privious.setNext(current.getNext()); } } return current; } public Link find(int key) { Link current = first; if (!isEmpty()) { while (current != null && current.getKey() <= key) { if (current.getKey() == key) { return current; } current = current.getNext(); } } return null; } public void displayList() { Link current = first; while (current != null) { current.printf(); current = current.getNext(); } } }
public class HashTable3 { private SortedList[] linkArray; private int arraySize; public HashTable3(int size) { this.arraySize = size; linkArray = new SortedList[arraySize]; for (int i = 0; i < arraySize; i++) { linkArray[i] = new SortedList(); } } public void display() { for (SortedList sl : linkArray) { sl.displayList(); } } public int hashFuc(int key) { return key % arraySize; } public void insert(Link link) { int hashVal = hashFuc(link.getKey()); linkArray[hashVal].insert(link); } public Link delete(int key) { int hashVal = hashFuc(key); return linkArray[hashVal].delete(key); } }
哈希字符串
之前介绍过如何把字符串转换成数字,例如 cats :key = 3*273 + 1*272 + 20*271 + 19*270 ;
得到的结果再哈希化成数组坐标:index = key % arraySize ;
public static void hashFunc1(String key, int arraySize) { int pow27 = 1; int totalVal = 0; for (int i = key.length() - 1; i >= 0; i--) { int letter = key.charAt(i) - 96; //a=1, b=2, c=3... totalVal = totalVal + letter * pow27; pow27 *= 27; } int hashVal = totalVal % arraySize; System.out.println("totalVal -> " + totalVal); System.out.println("hashVal -> " + hashVal); }
hashFunc1()方法并无那么高效,循环中有两次相乘和一次相加。还有一种 Horner 方法的数学恒等式取代乘法:
val4*n4 + val3*n3 + val2*n2 + val1*n1 + val0*n0 ——> (((val4*n + val3)*n + val2)*n + val1)*n+val0
public static void hashFunc2(String key, int arraySize) { int totalVal = key.charAt(0) - 96; for (int i = 1; i < key.length(); i++) { int letter = key.charAt(i) - 96; totalVal = totalVal * 27 + letter; } int hashVal = totalVal % arraySize; System.out.println("totalVal -> " + totalVal); System.out.println("hashVal -> " + hashVal); }
但是,以上的算法不能处理大于7个字符的字符串,更长的字符会导致 totalVal 超出 int 的类型范围,如果使用 long 也会有可能导致溢出。
在 Horner 公式的每一步,都可以应用取模操作符(%),最后得出的 hashVal 也是一样的,可以避免溢出。
public static void hashFunc3(String key, int arraySize) { int hashVal = 0; for (int i = 0; i < key.length(); i++) { int letter = key.charAt(i) - 96; hashVal = (hashVal * 27 + letter) % arraySize; } System.out.println("hashVal -> " + hashVal); }