1. 程式人生 > >java實現自定義雜湊表

java實現自定義雜湊表

## 雜湊表實現原理 ***雜湊表底層是使用陣列實現的,因為陣列使用下標查詢元素很快。所以實現雜湊表的關鍵就是把某種資料型別通過計算變成陣列的下標(這個計算就是hashCode()函式*** #### 比如,你怎麼把一個字串轉化成整數下標呢? - 可以把每個字元的ASCII對應的數字相加作為下標,比如"abc"=(a-96)+(b-96)+(c-96),'a'的ASCII是97;這種方式的缺點就是雜湊值很容易重複,比如aaa,abc,cab - 也可以使用冪的連乘,![](https://img2020.cnblogs.com/blog/1983810/202004/1983810-20200426100659071-42501053.png)保證不同字串算出來的雜湊值不一樣,這種方式的缺點是會算出來的雜湊值會發生數值越界 - 解決越界問題可以使用大數運算,java裡的BitInt ## 實現 #### 首先建立資料型別 ``` java 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類 ``` java 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(); } } ``` #### 測試 ``` java 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()); } } ``` ![](https://img2020.cnblogs.com/blog/1983810/202004/1983810-20200426102552836-105945395.png) 發現tc把a的位置給佔用了 ## 衝突解決 #### 為什麼會有衝突呢?因為我壓縮了可選值(進行了取模運算),比如我想把1和101個元素放到大小為10的陣列,對10取模後下標都是1,肯定會發生衝突 ### 開放地址法 ***1把一號位置佔用了,101就看2號位置有沒有被佔用,直到找到空位置,然後插入。主要101原本想插入的位置和最終插入位置一定是連續的,中間不會有空位置*** 此時需要修改一下插入刪除和查詢方法 ``` java 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(); } } ``` #### 測試 ![](https://img2020.cnblogs.com/blog/1983810/202004/1983810-20200426103209788-45409234.png) ![](https://img2020.cnblogs.com/blog/1983810/202004/1983810-20200426103222013-1047613252.png) 發現衝突問題解決了 ### 鏈地址法: ***實現原理,將一個個連結串列作為陣列的元素,當發生衝突就將衝突元素連結到對應的連結串列後面*** ***不同的雜湊值對應一個不同的連結串列,雜湊值相同的串在一起*** #### 連結串列結構,實現增刪查功能 ``` java public class LinkedNode { public Info info; public LinkedNode next; public LinkedNode(Info info) { this.info = info; } } ``` java 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; } } ``` #### 測試 ![](https://img2020.cnblogs.com/blog/1983810/202004/1983810-20200426104048210-1693294533.png) ![](https://img2020.cnblogs.com/blog/1983810/202004/1983810-20200426104048260-1912441780.png) 發現衝突解決了 ## 總結 雜湊表的本質是陣列,學會hashCode的實現方式,資料的壓縮,掌握解決衝突的倆種辦法 重點是鏈地址法,比開放地址法高