- 讀博文《PHP 陣列的雜湊碰撞攻擊》,藉此機會深入學習雜湊表相關知識,加深理解!
- 學習自《雜湊表(HashTable)的深入理解及實際演練》 原文地址
雜湊表(HashTable)概念
在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係H,以函式H(key)作為關鍵字為key的記錄在表中的位置,這個對應關係H稱為雜湊(Hash)函式(又稱雜湊函式),按這個思想建立的表為雜湊表(HashTable)。
雜湊函式的構建
1、直接定址法:取關鍵字key的一個線性函式為雜湊函式,即:H(key)=a×key+b,其中a、b為常數,且a≠0。
舉例:學生表關鍵字學號與儲存位置的雜湊函式:H(key)=key-1000。
2、數字分析法:若關鍵字是r進位制數,且可預知全部可能出現的關鍵字值,則可取關鍵字中若干位構成雜湊地址。
舉例 手機號碼的後四位做為雜湊地址
3、平方取中法:若關鍵字較短,則可先對關鍵字值求平方,然後取運算結果的中間幾位為雜湊地址。
舉例:
將一組關鍵字(0100,0110,1010,1001,0111)平方後得(0010000,0012100,1020100,1002001,0012321)若取表長為1000,則可取中間的三位數作為雜湊地址集: (100,121,201,020,123)。
4、摺疊法:將關鍵字值分割成位數相同的幾個部分,然後取這幾部分的疊加和(捨去進位)作為雜湊地址。
5、除留餘數法:取關鍵字被某個不大於雜湊表表長 m 的數 p 除後所得餘數為雜湊地址。(一般情況下,p 應為質數)
舉例:
設有一組關鍵字如下:(19,14,23,01,68,20,84,27,55,11,10,79),H(key)=key%13 。
雜湊函式構建的程式碼演練
- 構建一個學生類:學號+姓名;
- 構建一個雜湊表類:構雜誌社存放學生記錄的順序陣列,並初始化;
利用簡單的除留餘數法,將學生學號的餘數做為雜湊值,並根據這個雜湊值,做為順序陣列的下標,存放學生記錄。
- 編寫主程式進行測試
/**
* 深入理解雜湊表HashTable的程式碼演練
*/
public class MyHashTable {
private Student[] addr;
private Integer addrCount;
MyHashTable() {
this.addrCount = 1000;
this.addr = new Student[this.addrCount];
for (int i = 0; i < addr.length; i++) {
this.addr[i] = new Student(null, null);
}
}
// 用除留餘數法進行雜湊值運算
private int hash(Integer key) {
return (key % this.addrCount);
}
// 根據鍵值新增學生記錄
public void add(Integer key, Student student) {
Integer addrIndex = hash(key);
addr[addrIndex] = student;
}
// 根據鍵值刪除學生記錄
public void remove(Integer key) {
Integer addrIndex = hash(key);
addr[addrIndex] = new Student(null, null);
}
public boolean exist(Integer key) {
Integer addrIndex = hash(key);
return (addr[addrIndex].getId() != null);
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("MyHashTable{");
for (int i = 0; i < addr.length; i++) {
stringBuilder.append("hash="+i+":");
stringBuilder.append(addr[i].toString()+",");
}
stringBuilder.append("}");
return stringBuilder.toString();
}
public static void main(String[] args) {
MyHashTable myHashTable = new MyHashTable();
myHashTable.add(1001, new Student(1001, "Jack"));
myHashTable.add(1002, new Student(1002, "Rose"));
myHashTable.add(1003, new Student(1003, "Mike"));
myHashTable.add(1004, new Student(1004, "Tom"));
myHashTable.add(1005, new Student(1005, "Mary"));
myHashTable.add(1017, new Student(1017, "Kate"));
if (myHashTable.exist(1001)){
myHashTable.remove(1001);
}
if (myHashTable.exist(1008)){
myHashTable.remove(1008);
}
System.out.println(myHashTable.toString());
}
}
/**
* 定義學生類
*/
class Student {
private Integer id;
private String name;
Student(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
雜湊地址衝突的處理
1、在以上編碼演練中,可能會碰到如下情況:學號為1001的學生與學號為2001的學生,通過除留餘數法,得到了同樣的雜湊地址,則後新增的學生記錄,會與同樣雜湊地址的記錄存在衝突。
衝突的處理辦法
1、開放定址法:為產生衝突的地址H(key)再求得一個地址序列,直到不衝突為止。
線性探測再雜湊
平方探測再雜湊
隨機探測再雜湊
2、鏈地址法:將所有按給定的雜湊函式求得的雜湊地址相同的關鍵字儲存在同一線性連結串列中,且使連結串列按關鍵字有序。
雜湊地址解決衝突的程式碼演練
/**
* 深入理解雜湊表HashTable的程式碼演練--接鏈法解決衝突
*/
public class MyHashTableLinked {
private LinkedList<Student>[] addr;
private Integer addrCount;
MyHashTableLinked() {
this.addrCount = 1000;
this.addr = (LinkedList<Student>[]) new LinkedList[addrCount];
for (int i = 0; i < addr.length; i++) {
this.addr[i] = new LinkedList<Student>();
}
}
// 用除留餘數法進行雜湊值運算
private int hash(Integer key) {
return (key % this.addrCount);
}
// 根據鍵值新增學生記錄
public void add(Integer key, Student student) {
Integer addrIndex = hash(key);
addr[addrIndex].add(student);
}
// 根據鍵值刪除學生記錄
public void remove(Integer key) {
Integer addrIndex = hash(key);
LinkedList<Student> list = addr[addrIndex];
if (list != null) {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Student student = (Student) iterator.next();
list.remove(student);
}
}
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("MyHashTable{\r\n");
for (int i = 0; i < addr.length; i++) {
Iterator iterator = addr[i].iterator();
if (iterator.hasNext()) {
stringBuilder.append("[hash=" + i + ":");
} else {
continue;
}
while (iterator.hasNext()) {
Student student = (Student) iterator.next();
stringBuilder.append(student.toString() + ",");
}
stringBuilder.append("]\r\n");
}
stringBuilder.append("}");
return stringBuilder.toString();
}
public static void main(String[] args) {
MyHashTableLinked myHashTableLinked = new MyHashTableLinked();
myHashTableLinked.add(1001, new Student(1001, "Jack"));
myHashTableLinked.add(1002, new Student(1002, "Rose"));
myHashTableLinked.add(1003, new Student(1003, "Mike"));
myHashTableLinked.add(1004, new Student(1004, "Tom"));
myHashTableLinked.add(1005, new Student(1005, "Mary"));
myHashTableLinked.add(2001, new Student(2001, "Jerry"));
System.out.println(myHashTableLinked.toString());
myHashTableLinked.remove(1001);
System.out.println(myHashTableLinked.toString());
}
}
雜湊表的查詢
- 決定雜湊表查詢效率的是雜湊函式、處理衝突的方法和雜湊表的裝填因子。
- 雜湊表的裝填因子越小,發生衝突的可能就越小,而儲存空間的利用率也就越低。
// 查詢
public Student find(Integer key){
Integer addrIndex = hash(key);
LinkedList<Student> list = addr[addrIndex];
if (list != null) {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Student student = (Student) iterator.next();
if (student.getId()!=null && key.equals(student.getId())){
return student;
}
}
}
return null;
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結