哈希表的java實現

1、爲何要用哈希表java

樹的操做一般須要O(N)的時間級,而哈希表中不管存有多少數據,它的插入和查找(有時包括刪除)只須要接近常量級的時間,即O(1)的時間級。算法

可是哈希表也有必定的缺點:它是基於數組的,數組建立後難以擴展。而某些哈希表在基本填滿時,性能降低明顯,因此事先必須清楚哈希表中將要存儲多少數據。並且目前沒有一種簡便的方法能夠對哈希表進行有序(從大到小或者從小到大)的遍歷,除非哈希表自己是有序的,但事實上這是違背哈希原則的。數組

綜合以上:當不須要有序遍歷數據,並且能夠提早預測須要存儲的數據項的數目,使用哈希表的結構是十分方便的。數據結構

2、哈希化dom

把巨大的整數(關鍵字)範圍壓縮到一個可接受的數組範圍內,便於存儲和查找。一般來講,咱們要存儲5000個數據,但數據的關鍵字範圍多是0-200000。咱們不可能去開闢200000的數組去存儲這5000個數據,這就須要一個函數把關鍵字和數組下標對應起來。這就是哈希函數。一般的作法是取餘操做。i=N%size;i爲下標,N爲關鍵字,size爲數組大小。不過一般來講,size設爲要存儲數據項數目的兩倍。函數

若是哈希表存滿時,須要擴展哈希表。咱們須要新建一個更大的數組來存儲數據,而後把原表中數據一一取出放入新表中。須要注意的是數據放入新表時須要從新用哈希函數計算哈希值,不能直接進行數組的複製,由於哈希函數的size已經變了。性能

一般而言咱們把哈希數組的容量設爲一個質數。首先來講假如關鍵字是隨機分佈的,那麼無所謂必定要模質數。但在實際中每每關鍵字有某種規律,例如大量的等差數列,那麼公差和模數不互質的時候發生碰撞的機率會變大,而用質數就能夠很大程度上回避這個問題。對於除法哈希表h(k)=k mod m,注意二進制數對取餘就是該二進制數最後r位數。這樣一來,Hash函數就和鍵值(用二進制表示)的前幾位數無關了,這樣咱們就沒有徹底用到鍵值的信息,這種選擇m的方法是很差的。因此好的方法就是用質數來表示m,使得這些質數,不太接近2的冪或者10的冪。spa

3、解決衝突設計

首先通常哈希表是不容許重複的關鍵字,不然查找函數只能返回最早查到的關鍵字,沒法找到全部的對應數據項。若是重寫查找函數讓它能夠找到全部的對應數據項,這又會使得不管是不是重複關鍵字,查找操做都要搜索整個表,很是耗時。code

存儲過程當中可能出現存儲的數據項關鍵字不一樣,但計算出來的哈希值是相同的,這就是衝突。

一般採用如下兩種方法來解決衝突。

一、開放地址法

直接在哈希表中找到一個空位,把衝突的數據項存進去。

二、鏈地址法

把哈希表中存儲的數據格式設爲鏈表,這樣能夠把衝突的數據放入對應位置的鏈表中便可。

4、開放地址法的java實現

根據在查找下一個空位置時採用的方法,能夠把開放地址法分爲三種:線性探測、二次探測和再哈希法。

一、線性探測法

線性探測就是根據數組下標一個挨着一個去檢測,直到找到一個空位置。

java實現:

DataItem類定義了哈希表存儲的數據內容和關鍵字。

package hash;

class DataItem    //hash表中存放的數據格式
{                                
private int iData;               // 設爲關鍵字
//--------------------------------------------------------------
public DataItem(int ii)          // 構造器
   { iData = ii; }
//--------------------------------------------------------------
public int getKey()  //獲取關鍵字
   { return iData; }
//--------------------------------------------------------------
}  // end class DataItem
////////////////////////////////////////////////////////////////


HashTable類做爲哈希表的實現

package hash;

class HashTable        //定義哈希表
{
private DataItem[] hashArray;    // 數組形式
private int arraySize;           //哈希表的大小
private DataItem nonItem;        // 刪除數據時,將被刪除的數據設爲nonItem
//-------------------------------------------------------------
public HashTable(int size)       //構造器,指定哈希表的大小
   {
   arraySize = size;
   hashArray = new DataItem[arraySize];
   nonItem = new DataItem(-1);   // 把nonItem的關鍵字設爲-1
   }
//-------------------------------------------------------------
public void displayTable()       //顯示哈希表
   {
   System.out.print("Table: ");
   for(int j=0; j<arraySize; j++)
      {
      if(hashArray[j] != null)
         System.out.print(hashArray[j].getKey() + " ");
      else
         System.out.print("** ");  //該位置沒有存數據
      }
   System.out.println("");
   }
//-------------------------------------------------------------
public int hashFunc(int key)
   {
   return key % arraySize;       // 哈希函數
   }
//-------------------------------------------------------------
public void insert(DataItem item) // 插入數據
// 默認表未滿,事實上哈希表是不容許存滿的,哈希表的大小比實際存儲的數據數要大。
   {
   int key = item.getKey();      // 獲取數據項的關鍵字,用於計算哈希值
   int hashVal = hashFunc(key);  // 計算哈希值
                                 // 當前位置存有數據而且該數據未被刪除
   while(hashArray[hashVal] != null &&
                   hashArray[hashVal].getKey() != -1)
      {
      ++hashVal;                 // 查找下一個位置
      hashVal %= arraySize;      // 到達表的末尾時,hashVal值變成1,。構成循環,從而能夠查找整個表
      }
   hashArray[hashVal] = item;    // 找到位置
   }  // end insert()
//-------------------------------------------------------------
public DataItem delete(int key)  // 根據關鍵字刪除數據
   {
   int hashVal = hashFunc(key);  // 根據關鍵字計算哈希值

   while(hashArray[hashVal] != null)  // 該位置存有數據
      {                               // 二者的關鍵字是否相同
      if(hashArray[hashVal].getKey() == key)
         {
         DataItem temp = hashArray[hashVal]; // 保存刪除的數據項,用於返回
         hashArray[hashVal] = nonItem;       // 刪除
         return temp;                        // 返回刪除的數據項
         }
      ++hashVal;                 // 關鍵字不相同,繼續查找下一個
      hashVal %= arraySize;      //循環
      }
   return null;                  // 未找到
   }  // end delete()
//-------------------------------------------------------------
public DataItem find(int key)    // 表中是否存在該關鍵字的數據項
   {
   int hashVal = hashFunc(key);  

   while(hashArray[hashVal] != null)  
      {                               
      if(hashArray[hashVal].getKey() == key)
         return hashArray[hashVal];   
      ++hashVal;              
      hashVal %= arraySize;      
      }
   return null;                
   }
//-------------------------------------------------------------
}  // end class HashTable
////////////////////////////////////////////////////////////////

HashTableApp類中包含了主程序

package hash;
import java.io.*;

class HashTableApp
{
public static void main(String[] args) throws IOException
   {
   DataItem aDataItem;
   int aKey, size, n, keysPerCell;
   System.out.print("Enter size of hash table: ");
   size = getInt();//從控制檯獲取一個整數做爲哈希表的大小
   System.out.print("Enter initial number of items: ");
   n = getInt();  //隨機生成n個數做爲數據存入哈希表
   keysPerCell = 10;//隨機生成函數的因子
                                
  HashTable theHashTable = new HashTable(size);//初始化

   for(int j=0; j<n; j++)        // 生成並插入
      {
      aKey = (int)(java.lang.Math.random() *
                                      keysPerCell * size);
      aDataItem = new DataItem(aKey);//封裝爲哈希表中的數據格式
      theHashTable.insert(aDataItem);//插入
      }

   while(true)                   
      {
      System.out.print("Enter first letter of ");
      System.out.print("show, insert, delete, or find: ");
      char choice = getChar();
      switch(choice)
         {
         case 's':
            theHashTable.displayTable();
            break;
         case 'i':
         System.out.print("Enter key value to insert: ");
            aKey = getInt();
            aDataItem = new DataItem(aKey);
            theHashTable.insert(aDataItem);
            break;
         case 'd':
            System.out.print("Enter key value to delete: ");
            aKey = getInt();
            theHashTable.delete(aKey);
            break;
         case 'f':
            System.out.print("Enter key value to find: ");
            aKey = getInt();
            aDataItem = theHashTable.find(aKey);
            if(aDataItem != null)
               {
               System.out.println("Found " + aKey);
               }
            else
               System.out.println("Could not find " + aKey);
            break;
         default:
            System.out.print("Invalid entry\n");
         }  // end switch
      }  // end while
   }  // end main()
//--------------------------------------------------------------
public static String getString() throws IOException
   {
   InputStreamReader isr = new InputStreamReader(System.in);
   BufferedReader br = new BufferedReader(isr);
   String s = br.readLine();
   return s;
   }
//--------------------------------------------------------------
public static char getChar() throws IOException
   {
   String s = getString();
   return s.charAt(0);
   }
//-------------------------------------------------------------
public static int getInt() throws IOException
   {
   String s = getString();
   return Integer.parseInt(s);
   }
//--------------------------------------------------------------
}  // end class HashTableApp
////////////////////////////////////////////////////////////////


二、二次探測法

線性探測法會發生集聚現象,即衝突數據項會集聚在一塊兒,緣由是查找空數據項是一步一步移動的。

二次探測法是爲了防止集聚產生的一種嘗試方法,思想是探測間隔較遠的單元,而不是臨近的單元。具體方法是把步長設爲探測次數的平方,好比第1次探測步長爲1,第2次爲4,第3次爲9以此類推。

可是二次探測法會產生二次集聚。一般不採用該方法,由於有更好的解決方案。

三、再哈希法

方法是對衝突的關鍵字用另外一個哈希函數計算其值,把結果做爲搜索時的步長。這就使得不一樣的關鍵字步長不一樣,避免了集聚現象。

第二個哈希函數必須具有如下條件:

(1)與第一個哈希函數不一樣

(2)不能得出結果爲0,不然步長爲0.

一般第二個哈希函數採用以下函數:

step=constant-(key%contant)

constant是一個質數且小於數組容量,key是關鍵字。step範圍在1-constant之間。

再哈希法要求表的容量是一個質數,這是爲了使查找操做能夠遍歷整個表。不然假設表的容量爲15,不是一個質數。而查找初始位置爲4,查找步長爲5,那麼每次查找都是固定的三個數,即下標爲9,14,4對應的數據。設爲質數能夠避免這種狀況。

再哈希法的java實現

package hashDouble;

class DataItem
{                                 
private int iData;                // 關鍵字
//--------------------------------------------------------------
public DataItem(int ii)           // 構造器
   { iData = ii; }
//--------------------------------------------------------------
public int getKey() //獲取關鍵字
   { return iData; }
//--------------------------------------------------------------
}  // end class DataItem
////////////////////////////////////////////////////////////////


package hashDouble;

class HashTable
{
private DataItem[] hashArray; 
private int arraySize;
private DataItem nonItem;        
//-------------------------------------------------------------
HashTable(int size)               // 構造器
   {
   arraySize = size;
   hashArray = new DataItem[arraySize];
   nonItem = new DataItem(-1);
   }
//-------------------------------------------------------------
public void displayTable()
   {
   System.out.print("Table: ");
   for(int j=0; j<arraySize; j++)
      {
      if(hashArray[j] != null)
         System.out.print(hashArray[j].getKey()+ " ");
      else
         System.out.print("** ");
      }
   System.out.println("");
   }
//-------------------------------------------------------------
public int hashFunc1(int key)
   {
   return key % arraySize;
   }
//-------------------------------------------------------------
public int hashFunc2(int key) //再哈希
   {
   return 5 - key % 5;
   }
//-------------------------------------------------------------
                                  
public void insert(int key, DataItem item)
// 假設表未滿
   {
   int hashVal = hashFunc1(key);  // 計算哈希值
   int stepSize = hashFunc2(key); // 計算步長
                                  
   while(hashArray[hashVal] != null &&
                   hashArray[hashVal].getKey() != -1)//非空且數據未刪除
      {
      hashVal += stepSize;        // 加步長
      hashVal %= arraySize;       // 循環到表頭
      }
   hashArray[hashVal] = item;     // 插入
   }  // end insert()
//-------------------------------------------------------------
public DataItem delete(int key)   // 刪除
   {
   int hashVal = hashFunc1(key);      //計算哈希值
   int stepSize = hashFunc2(key);     // 計算步長

   while(hashArray[hashVal] != null)  // 非空
      {                               
      if(hashArray[hashVal].getKey() == key)//找到
         {
         DataItem temp = hashArray[hashVal]; 
         hashArray[hashVal] = nonItem;       
         return temp;                       
         }
      hashVal += stepSize;           
      hashVal %= arraySize;           
      }
   return null;                   // 沒法找到
   }  // end delete()
//-------------------------------------------------------------
public DataItem find(int key)     // 查找
// 假設表未滿
   {
   int hashVal = hashFunc1(key);       
   int stepSize = hashFunc2(key);     

   while(hashArray[hashVal] != null)  // 非空
      {                               
      if(hashArray[hashVal].getKey() == key)
         return hashArray[hashVal];   // 找到返回
      hashVal += stepSize;            // 加步長
      hashVal %= arraySize;          
      }
   return null;                   // can't find item
   }
//-------------------------------------------------------------
}  // end class HashTable
////////////////////////////////////////////////////////////////


package hashDouble;
import java.io.*;
class HashDoubleApp
{
public static void main(String[] args) throws IOException
   {
   int aKey;
   DataItem aDataItem;
   int size, n;
                             
   System.out.print("Enter size of hash table: ");
   size = getInt();
   System.out.print("Enter initial number of items: ");
   n = getInt();
                           
   HashTable theHashTable = new HashTable(size);

   for(int j=0; j<n; j++)     
      {
      aKey = (int)(java.lang.Math.random() * 2 * size);
      aDataItem = new DataItem(aKey);
      theHashTable.insert(aKey, aDataItem);
      }

   while(true)               
      {
      System.out.print("Enter first letter of ");
      System.out.print("show, insert, delete, or find: ");
      char choice = getChar();
      switch(choice)
         {
         case 's':
            theHashTable.displayTable();
            break;
         case 'i':
            System.out.print("Enter key value to insert: ");
            aKey = getInt();
            aDataItem = new DataItem(aKey);
            theHashTable.insert(aKey, aDataItem);
            break;
         case 'd':
            System.out.print("Enter key value to delete: ");
            aKey = getInt();
            theHashTable.delete(aKey);
            break;
         case 'f':
            System.out.print("Enter key value to find: ");
            aKey = getInt();
            aDataItem = theHashTable.find(aKey);
            if(aDataItem != null)
               System.out.println("Found " + aKey);
            else
               System.out.println("Could not find " + aKey);
            break;
         default:
            System.out.print("Invalid entry\n");
         }  // end switch
      }  // end while
   }  // end main()
//--------------------------------------------------------------
public static String getString() throws IOException
   {
   InputStreamReader isr = new InputStreamReader(System.in);
   BufferedReader br = new BufferedReader(isr);
   String s = br.readLine();
   return s;
   }
//--------------------------------------------------------------
public static char getChar() throws IOException
   {
   String s = getString();
   return s.charAt(0);
   }
//-------------------------------------------------------------
public static int getInt() throws IOException
   {
   String s = getString();
   return Integer.parseInt(s);
   }
//--------------------------------------------------------------
}  // end class HashDoubleApp
////////////////////////////////////////////////////////////////

5、鏈地址法的java實現

由於鏈地址法採用鏈表做爲表中的數據格式,因此容許存儲相同的關鍵字數據。並且咱們能夠把鏈設爲有序鏈表。

Link類定義了鏈表結構

package hashChain;

class Link        //定義鏈表
{                                   // 能夠動態存儲數據,擴充容量
private int iData;                  // 關鍵字
public Link next;                   // 連接到下一個
//-------------------------------------------------------------
public Link(int it)                 //構造器
   { iData= it; }
//-------------------------------------------------------------
public int getKey()       //獲取關鍵字
   { return iData; }
//-------------------------------------------------------------
public void displayLink()           // 顯示
   { System.out.print(iData + " "); }
}  // end class Link
////////////////////////////////////////////////////////////////

SortedList定義了有序鏈表

package hashChain;

class SortedList   //有序鏈表
{
private Link first;               // 鏈表頭
//-------------------------------------------------------------
public void SortedList()          // 構造器
   { first = null; }
//-------------------------------------------------------------
public void insert(Link theLink)  // 插入並有序
   {
   int key = theLink.getKey();
   Link previous = null;          // 前一個數據項
   Link current = first;
                                 
   while( current != null && key > current.getKey() )//非空且關鍵字大於當前數據關鍵字
      {                          
      previous = current; //繼續查找下一個
      current = current.next;     
      }
   if(previous==null)             // 若是表爲空
      first = theLink;            //    表頭指向該數據項
   else                           // 表非空
      previous.next = theLink;    //   key<current.getKey()時,數據項應插入previous後,previous-->theLink
   theLink.next = current;        // theLink --> current
   }  // end insert()
//-------------------------------------------------------------
public void delete(int key)       //刪除
   {                         
   Link previous = null;        
   Link current = first;
                              
   while( current != null && key != current.getKey() )//未找到
      {                        
      previous = current;
      current = current.next;     // 查找下一個
      }
                               
   if(previous==null)             //  要刪除數據項爲表頭
      first = first.next;         //      刪除表頭
   else                           //   不是表頭
      previous.next = current.next; //    刪除current
   }  // end delete()
//-------------------------------------------------------------
public Link find(int key)         // 查找
   {
   Link current = first;       
                                
   while(current != null &&  current.getKey() <= key)
      {                      
      if(current.getKey() == key)    // 找到
         return current;          // 返回
      current = current.next;     //未找到繼續查找下一個
      }
   return null;               //未找到
   }  // end find()
//-------------------------------------------------------------
public void displayList()//顯示
   {
   System.out.print("List (first-->last): ");
   Link current = first;     
   while(current != null)   
      {
      current.displayLink();  
      current = current.next;  
      }
   System.out.println("");
   }
}  // end class SortedList
////////////////////////////////////////////////////////////////


HashTable定義哈希表,表中數據爲SortedList形式

package hashChain;

class HashTable
{
private SortedList[] hashArray;   
private int arraySize;
//-------------------------------------------------------------
public HashTable(int size)        //構造器
   {
   arraySize = size;
   hashArray = new SortedList[arraySize];  // 初始化數組,數組中存儲的是鏈表
   for(int j=0; j<arraySize; j++)          // 初始化每一個數組元素
      hashArray[j] = new SortedList();  
   }
//-------------------------------------------------------------
public void displayTable()
   {
   for(int j=0; j<arraySize; j++) 
      {
      System.out.print(j + ". "); 
      hashArray[j].displayList(); 
      }
   }
//-------------------------------------------------------------
public int hashFunc(int key)      // 計算哈希值
   {
   return key % arraySize;
   }
//-------------------------------------------------------------
public void insert(Link theLink)  // 插入數據
   {
   int key = theLink.getKey();  //獲取關鍵字
   int hashVal = hashFunc(key);   // 計算關鍵字哈希值
   hashArray[hashVal].insert(theLink); // 插入哈希表中對應的位置
   }  // end insert()
//-------------------------------------------------------------
public void delete(int key)       // 根據關鍵字刪除數據
   {
   int hashVal = hashFunc(key);   // 計算關鍵字哈希值
   hashArray[hashVal].delete(key); // 刪除哈希表中對應數據
   }  // end delete()
//-------------------------------------------------------------
public Link find(int key)         // 查找
   {
   int hashVal = hashFunc(key);   
   Link theLink = hashArray[hashVal].find(key);  
   return theLink;               
   }
//-------------------------------------------------------------
}  // end class HashTable
////////////////////////////////////////////////////////////////
主函數
package hashChain;
import java.io.*;

class HashChainApp
{
public static void main(String[] args) throws IOException
   {
   int aKey;
   Link aDataItem;
   int size, n, keysPerCell = 100;
   System.out.print("Enter size of hash table: ");
   size = getInt();
   System.out.print("Enter initial number of items: ");
   n = getInt();
   HashTable theHashTable = new HashTable(size);

   for(int j=0; j<n; j++)        
      {
      aKey = (int)(java.lang.Math.random() *
                                       keysPerCell * size);
      aDataItem = new Link(aKey);
      theHashTable.insert(aDataItem);
      }
   while(true)                 
      {
      System.out.print("Enter first letter of ");
      System.out.print("show, insert, delete, or find: ");
      char choice = getChar();
      switch(choice)
         {
         case 's':
            theHashTable.displayTable();
            break;
         case 'i':
            System.out.print("Enter key value to insert: ");
            aKey = getInt();
            aDataItem = new Link(aKey);
            theHashTable.insert(aDataItem);
            break;
         case 'd':
            System.out.print("Enter key value to delete: ");
            aKey = getInt();
            theHashTable.delete(aKey);
            break;
         case 'f':
            System.out.print("Enter key value to find: ");
            aKey = getInt();
            aDataItem = theHashTable.find(aKey);
            if(aDataItem != null)
               System.out.println("Found " + aKey);
            else
               System.out.println("Could not find " + aKey);
            break;
         default:
            System.out.print("Invalid entry\n");
         }  // end switch
      }  // end while
   }  // end main()
//--------------------------------------------------------------
public static String getString() throws IOException
   {
   InputStreamReader isr = new InputStreamReader(System.in);
   BufferedReader br = new BufferedReader(isr);
   String s = br.readLine();
   return s;
   }
//-------------------------------------------------------------
public static char getChar() throws IOException
   {
   String s = getString();
   return s.charAt(0);
   }
//-------------------------------------------------------------
public static int getInt() throws IOException
   {
   String s = getString();
   return Integer.parseInt(s);
   }
//--------------------------------------------------------------
}  // end class HashChainApp
////////////////////////////////////////////////////////////////


6、如何設計哈希函數

一、不使用無用數據項

關鍵字的選取時,要提出原始數據中的無用數據項,例如起始位、校驗位、結束位等,由於這些數據位沒有攜帶信息。

二、使用全部的有用數據位

全部的有用數據位在哈希函數中都應當有體現。不要使用前四位或者後五位等其餘方法。

三、使用質數做爲取模運算的基數。

若關鍵字徹底隨機分佈,質數和非質數的表現差很少。可是當關鍵字不是隨機分佈時,就應該使用質數做爲哈希表的大小。使用質數能夠是關鍵字較爲平均的映射到哈希表的各個位置。

7、開放地址法和鏈地址法的比較

開放地址法在錶快滿時,性能有明顯降低,且對哈希表進行擴展時操做複雜。鏈地址法須要設計鏈表類,可是不會隨着數據項的增多致使性能快速降低,並且能夠動態擴展哈希表。


參考文獻: java數據結構與算法(第二版)

相關文章
相關標籤/搜索