哈希表實現原理
哈希表底層是使用數組實現的,因為數組使用下標查找元素很快。所以實現哈希表的關鍵就是把某種數據類型通過計算變成數組的下標(這個計算就是hashCode()函數***
怎么把一個字符串轉化成整數下標呢?
- 可以把每個字符的ASCII對應的數字相加作為下標,比如"abc"=(a-96)+(b-96)+(c-96),'a'的ASCII是97;這種方式的缺點就是哈希值很容易重復,比如aaa,abc,cab
- 也可以使用冪的連乘,
保證不同字符串算出來的哈希值不一樣,這種方式的缺點是會算出來的哈希值會發生數值越界
- 解決越界問題可以使用大數運算,java里的BitInt
實現
首先創建數據類型
package dataS.hash;
import java.util.Objects;
public class Info {
//員工號
private String key;
//員工值
private String value;
public Info(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "Info{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Info info = (Info) o;
return Objects.equals(key, info.key) &&
Objects.equals(value, info.value);
}
}
創建HashTable類
package dataS.hash;
import java.math.BigInteger;
public class HashTable {
private Info[] arrays;
/**
* 默認構造方法,默認數組大小100
*/
public HashTable() {
this.arrays = new Info[100];
}
/**
* 指定大小
*/
public HashTable(int maxsize){
this.arrays=new Info[maxsize];
}
/**
* 插入數據,直接把員工號作為數組索引
*/
public void insert(Info info){
this.arrays[hashCode(info.getKey())]=info;
}
/**
* 查找數據,直接把員工號作為數組索引
*/
public Info find(String key){
return arrays[hashCode(key)];
}
public int hashCode(String key){
return hash3(key);
}
public int hash1(String key){
//1.將字母轉化成ASCII,然后相加
int hashvalue=0;
for (int i = 0; i < key.length(); i++) {
//a是97,其他字母減去97就是字母對應的數字
int letter=key.charAt(i)-96;
hashvalue+= letter;
}
//取模可以壓縮可選值,比如想把100個可選擇壓縮到0-9,對數組長度取模
return hashvalue%arrays.length;
}
public int hash2(String key){
//2.冪的連乘,這里可能hashvalue的值超過范圍,使用long也不行,可以用bigint
int hashvalue=0;
int pow27=1;
for (int i = 0; i < key.length(); i++) {
//比如abc,1*27^0+2*27^1+2*27^2
int letter=key.charAt(i)-96;
hashvalue+= letter*pow27;
pow27*=27;
}
return hashvalue%arrays.length;
}
public int hash3(String key){
//3.用bigint
BigInteger hashvalue=new BigInteger("0");
BigInteger pow27=new BigInteger("1");
for (int i = 0; i < key.length(); i++) {
//比如abc,1*27^0+2*27^1+3*27^2
int letter=key.charAt(i)-96;
//把letter用bigint包裝起來
BigInteger bigLetter=new BigInteger(letter+"");
hashvalue=hashvalue.add(bigLetter.multiply(pow27));
pow27=pow27.multiply(new BigInteger(27+""));
}
return hashvalue.mod(new BigInteger(arrays.length+"")).intValue();
}
}
測試
package dataS.hash;
public class HashTest {
public static void main(String[] args) {
HashTable hashTable=new HashTable();
hashTable.insert(new Info("a","111"));
hashTable.insert(new Info("tc","222"));
hashTable.insert(new Info("cba","333"));
System.out.println(hashTable.find("a").getValue());
System.out.println(hashTable.find("tc").getValue());
System.out.println(hashTable.find("cba").getValue());
}
}
發現tc把a的位置給占用了
沖突解決
為什么會有沖突呢?因為我壓縮了可選值(進行了取模運算),比如我想把1和101個元素放到大小為10的數組,對10取模后下標都是1,肯定會發生沖突
開放地址法
把一號位置占用了,101就看2號位置有沒有被占用,直到找到空位置,然后插入。主要101原本想插入的位置和最終插入位置一定是連續的,中間不會有空位置
修改插入刪除和查找方法
package dataS.hashTwo;
import java.math.BigInteger;
public class HashTable {
private Info[] arrays;
/**
* 默認構造方法,實現哈希表的本質是哈希函數將不同類型的數據轉化成數組下表
*/
public HashTable() {
this.arrays = new Info[100];
}
/**
* 指定大小
*/
public HashTable(int maxsize){
this.arrays=new Info[maxsize];
}
/**
* 插入數據,直接把員工號作為數組索引
*/
public void insert(Info info){
String key=info.getKey();
//關鍵字對應的哈希值,將要作為下標
int hashValue=hash3(key);
//如果被占用,並且key對應的value也不為空(因為刪除的時候是刪除info對象里的value,而不是全部)
while (arrays[hashValue]!=null&&arrays[hashValue].getValue()!=null){
//一直找到一個沒被占用的
hashValue++;
//比如99和599哈希值取模后都是99,99加1后數組會越界,但是前面還有空的位置
hashValue%=arrays.length;
//直到整個數組都填滿
}
arrays[hashValue]=info;
}
/**
* 查找數據,直接把員工號作為數組索引
*/
public Info find(String key){
int hashValue=hash3(key);
//從第一次的位置,到最終插入位置是連續的
while (arrays[hashValue]!=null){
//如果key值相等說明找到了
if(arrays[hashValue].getKey().equals(key))
return arrays[hashValue];
hashValue++;
hashValue%=arrays.length;
}
return null;
}
public Info delete(String key){
int hashValue=hash3(key);
//從第一次的位置,到最終插入位置是連續的
while (arrays[hashValue]!=null){
//如果key值相等說明找到了,將Info的value值空
if(arrays[hashValue].getKey().equals(key)){
Info info=arrays[hashValue];
arrays[hashValue].setValue(null);
return info;
}
hashValue++;
hashValue%=arrays.length;
}
return null;
}
public int hashCode(String key){
return hash3(key);
}
public int hash1(String key){
//1.將字母轉化成ASCII,然后相加
int hashvalue=0;
for (int i = 0; i < key.length(); i++) {
//a是97,其他字母減去97就是字母對應的數字
int letter=key.charAt(i)-96;
hashvalue+= letter;
}
//取模可以壓縮可選值,比如想把100個可選擇壓縮到0-9,對數組長度取模
return hashvalue%arrays.length;
}
public int hash2(String key){
//2.冪的連乘,這里可能hashvalue的值超過范圍,使用long也不行,可以用bigint
int hashvalue=0;
int pow27=1;
for (int i = 0; i < key.length(); i++) {
//比如abc,1*27^0+2*27^1+2*27^2
int letter=key.charAt(i)-96;
hashvalue+= letter*pow27;
pow27*=27;
}
return hashvalue%arrays.length;
}
public int hash3(String key){
//3.用bigint
BigInteger hashvalue=new BigInteger("0");
BigInteger pow27=new BigInteger("1");
for (int i = 0; i < key.length(); i++) {
//比如abc,1*27^0+2*27^1+3*27^2
int letter=key.charAt(i)-96;
//把letter用bigint包裝起來
BigInteger bigLetter=new BigInteger(letter+"");
hashvalue=hashvalue.add(bigLetter.multiply(pow27));
pow27=pow27.multiply(new BigInteger(27+""));
}
return hashvalue.mod(new BigInteger(arrays.length+"")).intValue();
}
}
測試
發現沖突問題解決了
鏈地址法:
實現原理,將一個個鏈表作為數組的元素,當發生沖突就將沖突元素鏈接到對應的鏈表后面
不同的哈希值對應一個不同的鏈表,哈希值相同的串在一起
鏈表結構,實現增刪查功能
public class LinkedNode {
public Info info;
public LinkedNode next;
public LinkedNode(Info info) {
this.info = info;
}
}
package dataS.hashThree;
public class Linked {
public LinkedNode head;
public Linked() {
head = null;
}
//插入節點,在頭結點之后插入.
//重點是不要讓節點丟失
public void insert(Info info) {
LinkedNode node = new LinkedNode(info);
if (head == null) {
head = node;
} else {
node.next = head.next;
// head.next=node;此處不應該這么寫,會形成環
head.next = node;
}
}
//在頭結點之后刪除一個元素
public LinkedNode delete() throws Exception {
if(head.next==null){
head=null;
return null;
}else{
LinkedNode tmp = head.next;
head.next = tmp.next;
return tmp;
}
}
//查找方法
public LinkedNode find(String key) {
LinkedNode tmp = head;
if(tmp==null)
return null;
while (!key.equals(tmp.info.getKey())){
if(tmp.next==null)
return null;
tmp=tmp.next;
}
return tmp;
}
//根據值來刪除元素
public LinkedNode deleteByvalue(String key){
LinkedNode ans = null;
LinkedNode pretmp = head;
LinkedNode tmp =head.next;
if(key.equals(head.info.getKey())){
ans=head;
head=head.next;
return ans;
}
while (tmp!=null){
if(key.equals(tmp.info.getKey())){
ans=tmp;
pretmp.next=tmp.next;
}
pretmp=pretmp.next;
tmp=tmp.next;
}
return ans;
}
}
哈希表
package dataS.hashThree;
import java.math.BigInteger;
public class HashTable {
private Linked[] arrays;
/**
* 默認構造方法,實現哈希表的本質是哈希函數將不同類型的數據轉化成數組下表
*/
public HashTable() {
this.arrays = new Linked[100];
}
/**
* 指定大小
*/
public HashTable(int maxsize){
this.arrays=new Linked[maxsize];
}
/**
* 插入數據,直接把員工號作為數組索引
*/
public void insert(Info info){
String key=info.getKey();
//關鍵字對應的哈希值,將要作為下標
int hashValue=hash3(key);
//
if(arrays[hashValue]==null){
arrays[hashValue]=new Linked();
}
//插入
arrays[hashValue].insert(info);
}
/**
* 查找數據,直接把員工號作為數組索引
*/
public Info find(String key){
int hashValue=hash3(key);
//從第一次的位置,到最終插入位置是連續的
LinkedNode node = arrays[hashValue].find(key);
if(node==null)
return null;
return node.info;
}
public Info delete(String key){
int hashValue=hash3(key);
//從第一次的位置,到最終插入位置是連續的
LinkedNode node = arrays[hashValue].deleteByvalue(key);
return node.info;
}
public int hashCode(String key){
return hash3(key);
}
public int hash1(String key){
//1.將字母轉化成ASCII,然后相加
int hashvalue=0;
for (int i = 0; i < key.length(); i++) {
//a是97,其他字母減去97就是字母對應的數字
int letter=key.charAt(i)-96;
hashvalue+= letter;
}
//取模可以壓縮可選值,比如想把100個可選擇壓縮到0-9,對數組長度取模
return hashvalue%arrays.length;
}
public int hash2(String key){
//2.冪的連乘,這里可能hashvalue的值超過范圍,使用long也不行,可以用bigint
int hashvalue=0;
int pow27=1;
for (int i = 0; i < key.length(); i++) {
//比如abc,1*27^0+2*27^1+2*27^2
int letter=key.charAt(i)-96;
hashvalue+= letter*pow27;
pow27*=27;
}
return hashvalue%arrays.length;
}
public int hash3(String key){
//3.用bigint
BigInteger hashvalue=new BigInteger("0");
BigInteger pow27=new BigInteger("1");
for (int i = 0; i < key.length(); i++) {
//比如abc,1*27^0+2*27^1+3*27^2
int letter=key.charAt(i)-96;
//把letter用bigint包裝起來
BigInteger bigLetter=new BigInteger(letter+"");
hashvalue=hashvalue.add(bigLetter.multiply(pow27));
pow27=pow27.multiply(new BigInteger(27+""));
}
return hashvalue.mod(new BigInteger(arrays.length+"")).intValue();
}
public int hash4(String key){
//3.使用開放地址法解決沖突
//當沖突發生,查找空位置插入,而不再用哈希函數得到數組下標
return 1;
}
}
測試
發現沖突解決了
總結
哈希表的本質是數組,學會hashCode的實現方式,數據的壓縮,掌握解決沖突的倆種辦法
重點是鏈地址法,比開放地址法高效簡潔