數據結構之散列函數


1、散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。提供了快速的插入和查找操作,其基於數組實現。​​其基本思想就是將關鍵字key均勻映射到散列表下標0~TableSize-1這個范圍之內的某個數。

2、散列函數構造方法:

  1>直接定址法:所謂直接定址法就是說,取關鍵字的某個線性函數值為散列地址,即

        優點:簡單、均勻,也不會產生沖突。             缺點:需要事先知道關鍵字的分布情況,適合查找表較小且連續的情況。

          由於這樣的限制,在現實應用中,此方法雖然簡單,但卻並不常用。

  2>數字分析法:如果關鍵字時位數較多的數字,比如11位的手機號"130****1234",其中前三位是接入號;中間四位是HLR識別號,表示用戶號的歸屬地;后四為才是真正的用戶號。如下圖所示。

    如果現在要存儲某家公司的登記表,若用手機號作為關鍵字,極有可能前7位都是相同的,選擇后四位成為散列地址就是不錯的選擇。若容易出現沖突,對抽取出來的數字再進行反轉、右環位移等。總的目的就是為了提供一個散列函數,能夠合理地將關鍵字分配到散列表的各個位置。

    數字分析法通過適合處理關鍵字位數比較大的情況,如果事先知道關鍵字的分布且關鍵字的若干位分布比較均勻,就可以考慮用這個方法。

    3>平方取中法: 這個方法計算很簡單,假設關鍵字是1234,那么它的平方就是1522756,再抽取中間的3位就是227,用做散列地址。

      平方取中法比較適合不知道關鍵字的分布,而位數又不是很大的情況。

    4>

3、哈希化:(1)直接將關鍵字作為索引;(2)​將字符串轉換成索引:將字符串轉換成ascii碼,然后進行相加;冪的連乘;壓縮可選值。

4、處理散列沖突的方法:

  4.1 開放定址法:所謂的開放定址法就是一旦發生了沖突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入。

  它的公式為:

    比如說,關鍵字集合為{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},表長為12。散列函數f(key) = key mod 12。

    當計算前5個數{12, 67, 56, 16, 25}時,都是沒有沖突的散列地址,直接存入,如下表所示。

    計算key = 37時,發現f(37) = 1,此時就與25所在的位置沖突。於是應用上面的公式f(37) = (f(37) + 1) mod 12 =2,。於是將37存入下標為2的位置。如下表所示。

    接下來22,29,15,47都沒有沖突,正常的存入,如下標所示。

    到了48,計算得到f(48) = 0,與12所在的0位置沖突了,不要緊,我們f(48) = (f(48) + 1) mod 12 = 1,此時又與25所在的位置沖突。於是f(48) = (f(48) + 2) mod 12 = 2,還是沖突......一直到f(48) = (f(48) + 6) mod 12 = 6時,才有空位,如下表所示。

 

    把這種解決沖突的開放定址法稱為線性探測法

    考慮深一步,如果發生這樣的情況,當最后一個key = 34,f(key) = 10,與22所在的位置沖突,可是22后面沒有空位置了,反而它的前面有一個空位置,盡管可以不斷地求余后得到結果,但效率很差。因此可以改進di=12, -12, 22, -22.........q2, -q2(q<= m/2),這樣就等於是可以雙向尋找到可能的空位置。對於34來說,取di = -1即可找到空位置了。另外,增加平方運算的目的是為了不讓關鍵字都聚集在某一塊區域。稱這種方法為二次探測法。

    還有一種方法,在沖突時,對於位移量di采用隨機函數計算得到,稱之為隨機探測法

    既然是隨機,那么查找的時候不也隨機生成di 嗎?如何取得相同的地址呢?這里的隨機其實是偽隨機數。偽隨機數就是說,如果設置隨機種子相同,則不斷調用隨機函數可以生成不會重復的數列,在查找時,用同樣的隨機種子,它每次得到的數列是想通的,相同的di 當然可以得到相同的散列地址。

    總之,開放定址法只要在散列表未填滿時,總是能找到不發生沖突的地址,是常用的解決沖突的方法。

  public void insert(Info info) {

    //獲得關鍵字

    String key = info.getKey();

    //關鍵字所自定的哈希數

    int hashVal = hashCode(key);

    //如果這個索引已經被占用,而且里面是一個未被刪除的數據

    while(arr[hashVal] != null && arr[hashVal].getName() != null) {

      //進行遞加

      ++hashVal;

      //循環

      hashVal %= arr.length;

    }

    arr[hashVal] = info;

  }

  public Info find(String key) {

    int hashVal = hashCode(key);

    while(arr[hashVal] != null) {

      if(arr[hashVal].getKey().equals(key)) {

        return arr[hashVal];

      }

      ++hashVal;

      hashVal %= arr.length;

    }

    return null;

  }

  public Info delete(String key) {

    int hashVal = hashCode(key);

    while(arr[hashVal] != null) {

      f(arr[hashVal].getKey().equals(key)) {

        Info tmp = arr[hashVal];

        tmp.setName(null);

        return tmp;

      }

      ++hashVal;

      hashVal %= arr.length;

    }

    return null;

  }

  4.2 再散列函數法:

     對於散列表來說,可以事先准備多個散列函數。

    這里RH就是不同的散列函數,可以把前面說的除留余數、折疊、平方取中全部用上。每當發生散列地址沖突時,就換一個散列函數計算。

    這種方法能夠使得關鍵字不產生聚集,但相應地也增加了計算的時間。

  4.3 鏈地址法:

  將所有關鍵字為同義詞的記錄存儲在一個單鏈表中,稱這種表為同義詞子表,在散列表中只存儲所有同義詞子表前面的指針。對於關鍵字集合{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},用前面同樣的12為余數,進行除留余數法,可以得到下圖結構。

 

 

    此時,已經不存在什么沖突換地址的問題,無論有多少個沖突,都只是在當前位置給單鏈表增加結點的問題。

    鏈地址法對於可能會造成很多沖突的散列函數來說,提供了絕不會出現找不到地址的保證。當然,這也就帶來了查找時需要遍歷單鏈表的性能損耗。


總結:開放地址法將所有結點均存放在散列表(Hash)T[0..m-1]中,鏈址法將互為同義詞的結點鏈成一個但鏈表,而將此鏈表的頭指針放在散列表T[0..m-1]中。與開放地址相比  鏈地址法有如下優點:1,鏈地址法處理沖突簡單,且無堆積現象,即非同義詞決不會發生沖突,因此平均查找長度較短。2,鏈地址法中鏈表的結點是動態申請的,故它更適合造表前無法確定表長的情況,3,開放定址法為了減少沖突要求填充因子較小,故結點規模較大時會浪費很多空間,而鏈地址法中填充因子可以大於1且結點較大時,拉鏈法中增加的指針域可以忽略不計,因此節省空間,4,鏈地址法構造的散列表刪除結點很方便,只需簡單的刪去鏈表上相應的結點即可。

     public class HashTable {

    private LinkList[] arr;

    public HashTable() {

      arr = new LinkList[100];

    }

    public HashTable(int maxSize) {

      arr = new LinkList[maxSize];

    }

    public void insert(Info info) {

      //獲得關鍵字

      String key = info.getKey();

      //關鍵字所自定的哈希數

      int hashVal = hashCode(key);

      if(arr[hashVal] == null) {

        arr[hashVal] = new LinkList();

      }

      arr[hashVal].insertFirst(info);

    }

    public Info find(String key) {

      int hashVal = hashCode(key);

      return arr[hashVal].find(key).info;

    }

    public Info delete(String key) {

      int hashVal = hashCode(key);

      return arr[hashVal].delete(key).info;

    }

    public int hashCode(String key) {

      //int hashVal = 0;

      //for(int i = key.length() - 1; i >= 0; i--) {

        //int letter = key.charAt(i) - 96;

        //hashVal += letter;

      //}

      //return hashVal;

      BigInteger hashVal = new BigInteger("0");

      BigInteger pow27 = new BigInteger("1");

      for(int i = key.length() - 1; i >= 0; i--) {

        int letter = key.charAt(i) - 96;

        BigInteger letterB = new BigInteger(String.valueOf(letter));

        hashVal = hashVal.add(letterB.multiply(pow27));

        pow27 = pow27.multiply(new BigInteger(String.valueOf(27)));

      }

      return hashVal.mod(new BigInteger(String.valueOf(arr.length))).intValue();

    }

  }

  public class LinkList {

    //頭結點

    private Node first;

    public LinkList() {

      first = null;

    }

    public void insertFirst(Info info) {

      Node node = new Node(info);

      node.next = first;

      first = node;

    }

    public Node deleteFirst() {

      Node tmp = first;

      first = tmp.next;

      return tmp;

    }

    public Node find(String key) {

      Node current = first;

      while(!key.equals(current.info.getKey())) {

        if(current.next == null) {

        return null;

      }

      current = current.next;

    }

    return current;

  }

  public Node delete(String key) {

    Node current = first;

    Node previous = first;

    while(!key.equals(current.info.getKey())) {

      if(current.next == null) {

        return null;

      }

      previous = current;

      current = current.next;

    }

     if(current == first) {

      first = first.next;

     } else {

        previous.next = current.next;

      }

    return current;

  }

}

  public class Node {

    //數據域

    public Info info;

    //指針域

    public Node next;

    public Node(Info info) {

      this.info = info;

    }

  }

   參考鏈接:http://blog.chinaunix.net/uid-26548237-id-3480645.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM