哈希表
基本概念:
散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
給定表M,存在函數f(key),對任意給定的關鍵字值key,代入函數後若能得到包含該關鍵字的記錄在表中的地址,則稱表M爲哈希(Hash)表,函數f(key)爲哈希(Hash) 函數。
一般想法:
出自Java數據結構書籍
理想的散列表數據結構只不過是一個包含一些項的具有固定大小的數組,通常查找是對項的某個部分(即數據域)進行的,這一部分就叫做關鍵字,例如,項可以由一個串和其他一些數據域組成,我們把表的大小叫做tablesize,並將其理解爲散列數據結構的一部分,而不僅僅是浮動於全局的某個變量,通常的習慣是讓表從0到擡不tablesize-1變化,每個關鍵字被映射到從0到tablesize-1這個範圍中的某一個數,並且被放到了適合的單元中,這個映射就叫做散列函數,這就是散列的基本想法,剩下的問題就是要選擇一個函數,決定當兩個關鍵字散列到同一個值的時候(哈希衝突),應該做什麼以及如何確定散裂散列表的大小?
散列函數:
出自Java數據結構書籍
通常,關鍵字是字符串:在這種情形下,散列函數需要仔細地選擇。
租種選擇方法是把字符串中字符的ASCI碼(或Unicode碼)值加起來。圖:1中的例程出現現這種策略。
圖1:
public static int hash(String key,int tableSize) {
int hashVal = 0;
for(int i = 0; i < key.length();i++) {
hashVal += key.charAt(i);
}
return hashVal % tableSize;
}
圖1中描述的散列函數實現起來簡單而且能夠很快地計算出答案。不過,如果表很大,函數將不會很好地分配關鍵字。例如,設TableSize= 10007(10007是素數),並設所有的關鍵字至多8個字符長。由於ASCII字符的值最多是127,因此散列函數只能假設值在0和1016之間,其中1016爲127 *8。顯然這不是一種均勻的分配。
另一個散列函數如圖2所示。這個散列函數假設Key至少有3個字符。值27表示英文字母表的字母外加一個空格的個數, 而729是27的平方。該函數只考查前三個字符,但是,假如它們是隨機的,而表的大小像前面那樣還是10007,那麼我們就會得到一個合理的均衡分佈。可是不巧的是,英文不是隨機的。雖然3個字符(忽略空格)有26的三次方= 17576種可能的組合,但查驗合理的足夠大的聯機詞典卻揭示: 3個字母的不同組合數實際只有2851。即使這些組合沒有衝突,也不過只有表的28%被真正散列到。因此,雖然很容易計算,但是當散列表具有合理大小的時候這個函數還是不合適的。
圖2:
public static int hash(String key,int tableSize) {
return (key.charAt(0) + 27 * key.charAt(1)+
729 * key.charAt(2)) % tableSize;
}
圖3列出了散列函數的第3種嘗試。這個散列函數涉及關鍵字中的所有字符,並且一般
分佈的很好,並將結果限制在適當的範圍內。程序根據Homer法則計算一個(37的)多項式函數。例如,計算hk=ko+37k1 +37的平方k2的另一種方式藉助於公式hk=((k2) *37 +k1)*37+k0進行。Homer法則將其打展到用於n次多項式。
這個散列函數利用到事實:允許溢出。這可能會引進負的數,因此在末尾有附加的測試。
圖3;
ublic static int hash(String key,int tableSize) {
int hashVal = 0;
for(int i = 0; i < key.length();i++) {
hashVal = 37 * hashVal + key.charAt(i);
}
hashVal %= tableSize;
if(hashVal < 0) {
hashVal += tableSize;
}
return hashVal;
}
圖3所描述的散列函數就表的分佈而言未必是最好的,但確實具有極其簡單的優點而且速度也很快。如果關鍵字特別長,那麼該散列函數計算起來將會花費過多的時間單的優點在這種情況下通常的經驗是不使用所有的字符,此時關鍵字的長度和性質將影響選擇。
例如、
關鍵字可能是完整的街道地址,散列函數可以包括街道地址的幾個字符,也許還有城市名和郵政的編碼的幾個字符
有些程序設計人員通過只使用奇數位置上的字符來實現他們的散列函數
有這麼一層想法;用計算散列函數節省下的時間來補償由此產生的對均勻收分佈函數的輕微干擾,這裏有這麼一層想法:
例題:
谷歌公司的面試題
有一個公司,當有新的員工來報道時,要求該員工的信息加入id,姓名地址,當輸入該員工的id時候,要求找到幹員工的所有信息。
要求:不使用數據庫,儘量節省內存,速度越快越好=》h哈希表(散列)
import java.util.Scanner;
public class HashTableTest {
public static void main(String[] args) {
HashTable hashTable = new HashTable(6);
int key ;
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println("1: 添加僱員");
System.out.println("2: 顯示僱員");
System.out.println("3: 查找僱員");
System.out.println("4: 刪除僱員");
System.out.println("5: 找到鏈表長度");
System.out.println("6: 修改僱員");
System.out.println("7: 退出");
key = scanner.nextInt();
switch (key) {
case 1:
System.out.println("輸入id");
int id = scanner.nextInt();
System.out.println("輸入名字");
String name = scanner.next();
System.out.println("輸入地址");
String address = scanner.next();
//創建 僱員
Employee emp = new Employee(id, name,address);
hashTable.add(emp);
break;
case 2:
hashTable.list();
break;
case 3:
System.out.println("請輸入要查找的id");
id = scanner.nextInt();
hashTable.findEmpById(id);
break;
case 4:
System.out.println("請輸入要刪除的id");
id = scanner.nextInt();
hashTable.deleteEmpById(id);;
break;
case 5:
System.out.println("請輸入要查找的鏈表的長度");
id = scanner.nextInt();
int length = hashTable.hashTableLeagth(id);
System.out.println("鏈表長度爲"+length);
break;
case 6:
System.out.println("請輸入要修改的id");
id = scanner.nextInt();
hashTable.findEmpById(id);
System.out.println("輸入名字");
String upadte_name = scanner.next();
System.out.println("輸入地址");
String upadte_address = scanner.next();
hashTable.updateEmpById(id, upadte_name, upadte_address);;
break;
case 7:
scanner.close();
System.exit(0);
default:
break;
}
}
}
}
//創建hashtable
class HashTable{
private EmpLinkedList[] empLinkedLists;
private int size;
public HashTable(int size) {
this.size = size;
empLinkedLists = new EmpLinkedList[size];
for(int i = 0; i < size; i++) {
empLinkedLists[i] = new EmpLinkedList();
}
}
//添加
public void add(Employee employee) {
int empLinkedListNo = hashFun(employee.id);
empLinkedLists[empLinkedListNo].add(employee);
}
//遍歷所有的鏈表 遍歷hashtable
public void list() {
for(int i =0;i<size;i++) {
empLinkedLists[i].list();
}
}
public int hashFun(int id) {
return id % size;
}
//根據輸入的id,查找僱員
public void findEmpById(int id) {
//使用散列函數確定到哪條鏈表查找
int empLinkedListNO = hashFun(id);
Employee emp = empLinkedLists[empLinkedListNO].findEmpById(id);
if(emp != null) {//找到
System.out.printf("在第%d條鏈表中找到 僱員 id = %d\n", (empLinkedListNO + 1), id);
}else{
System.out.println("在哈希表中,沒有找到該僱員~");
}
}
//根據輸入的id,修改僱員
public void updateEmpById(int id,String name,String address) {
//使用散列函數確定到哪條鏈表查找
int empLinkedListNO = hashFun(id);
Employee emp = empLinkedLists[empLinkedListNO].updateEmpById(id, name, address);
if(emp != null) {//找到
System.out.printf("在第%d條鏈表中修改 僱員 id = %d\n", (empLinkedListNO + 1), id);
}else{
System.out.println("在哈希表中,沒有找到該僱員~");
}
}
//刪除
public void deleteEmpById(int id) {
//使用散列函數確定到哪條鏈表查找
int empLinkedListNO = hashFun(id);
Employee emp = empLinkedLists[empLinkedListNO].deleteByID(id);
if(emp != null) {//找到
System.out.printf("在第%d條鏈表中刪除 僱員 id = %d\n", (empLinkedListNO + 1), id);
}else{
System.out.println("在哈希表中,沒有找到該僱員~");
}
}
//長度
public int hashTableLeagth(int id) {
if(id<size) {
int empLinkedListNO = hashFun(id);
int length = empLinkedLists[empLinkedListNO].length();
return length;
}
else {
System.out.println("hashtable最大隻有"+size+"條列表");
return size;
}
}
}
class Employee{
public int id;
public String name;
public String address;
public Employee next;
public Employee(int id,String name,String address) {
super();
this.id = id;
this.name = name;
this.address = address;
}
}
class EmpLinkedList{
//設置一個頭指針headEmployee
//執行第一個Employee,因此我們這個鏈表的head 是指向第一個employee
private Employee headEmployee;
//添加僱員到鏈表
//說明
//1.假定 當添加僱員 id是自增加
public void add(Employee employee) {
if(headEmployee==null) {
headEmployee = employee;
return;
}
Employee curEmployee = headEmployee;
while(true) {
if(curEmployee.next == null) {
break;
}
curEmployee = curEmployee.next;
}
curEmployee.next = employee;
}
//遍歷
public void list() {
if(headEmployee==null) {
System.out.println("當前鏈表爲空");
return;
}
System.out.println("當前鏈表信息爲");
Employee curEmployee = headEmployee;
while(true) {
System.out.printf("--> id=%d name=%s,address=%s\t",curEmployee.id,curEmployee.name,curEmployee.address);
if(curEmployee.next == null) {
break;
}
curEmployee = curEmployee.next;
}
System.out.println();
}
//找到該員工信息
public Employee findEmpById(int id) {
//判斷鏈表是否爲空
if(headEmployee == null) {
System.out.println("鏈表爲空");
return null;
}
//輔助指針
Employee curEmp = headEmployee;
while(true) {
if(curEmp.id == id) {//找到
break;//這時curEmp就指向要查找的僱員
}
//退出
if(curEmp.next == null) {//說明遍歷當前鏈表沒有找到該僱員
curEmp = null;
break;
}
curEmp = curEmp.next;//以後
}
return curEmp;
}
//修改該員工信息
public Employee updateEmpById(int id,String name,String address) {
//判斷鏈表是否爲空
if(headEmployee == null) {
System.out.println("鏈表爲空");
return null;
}
//輔助指針
Employee curEmp = headEmployee;
while(true) {
if(curEmp.id == id) {//找到
break;//這時curEmp就指向要查找的僱員
}
//退出
if(curEmp.next == null) {//說明遍歷當前鏈表沒有找到該僱員
curEmp = null;
break;
}
curEmp = curEmp.next;//以後
}
curEmp.name = name;
curEmp.address = address;
return curEmp;
}
//刪除該員工
public Employee deleteByID(int id) {
if(headEmployee == null) {
System.out.println("鏈表爲空");
return null;
}
//輔助指針
Employee curEmp = headEmployee;
Employee indexEmp = headEmployee;
int x= 0;
for(int i =0;i<length();i++) {
x=i;
if(curEmp.id == id) {//找到
break;//這時curEmp就指向要查找的僱員
}
curEmp = curEmp.next;
}
for(int i =0;i<length();i++) {
if(x==0) {
headEmployee=curEmp.next;
}
if(i==(x-1)) {
indexEmp.next = curEmp.next;
//indexEmp.next = indexEmp.next.next;
break;
}
indexEmp=indexEmp.next;
}
return curEmp;
}
//找到hashtable裏數組中鏈表的長度
public int length() {
if(headEmployee == null) {
System.out.println("鏈表爲空");
return 0;
}
//輔助指針
Employee curEmp = headEmployee;
int length = 1;
while(true) {
//退出
if(curEmp.next== null) {//說明遍歷當前鏈表沒有找到該僱員
break;
}
curEmp = curEmp.next;//以後
length++;
}
return length;
}
}