數據結構與算法(十):哈希表

1、什麼是哈希表

1.概述

哈希表(Hash table,也叫散列表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它經過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度這個映射函數叫作散列函數,存放記錄的數組叫作散列表java

通俗的理解一下:node

  • 若是咱們有n個元素要存儲,那咱們就用l個內存單元來存儲他們
  • 而後咱們有一個哈希函數f(x),咱們把元素n用函數計算獲得哈希值,也就是f(n)
  • f(n)就是存儲元素n的那個內存單位的位置,也就是元素在l中的下標

2.爲何哈希表查詢速度快

理解了哈希表的基本思路,咱們也就不難理解爲何哈希表查詢效率高了:數組

因爲每一個元素都能經過哈希函數直接計算得到地址,因此查找消耗時間很是少。數據結構

舉個例子:ide

咱們有哈希函數f(n)=n%3,現有元素{1,2,3},咱們使用哈希函數分別得到其哈希值,並把哈希值做爲下標存入一個數組,函數

也就是放f(1)=1,f(2)=2,f(3)=0,若是使用傳統線性查找,須要遍歷四次,而使用哈希函數計算並查找,只須要一步就能找到,this

能夠看得出,理想狀況下,哪怕數列再長,找到某個元素都只須要一步。.net

3.哈希衝突

按照上文的例子,數列{1,2,3}經過哈希函數f(n)=n%3能夠計算出哈希值,可是若是出現兩個元素的哈希值相同就會出現哈希衝突,3d

好比f(1)和f(4)都會算出1,這個時候顯然不可能上上面同樣經過一個一維數組直接存儲。code

對此咱們有兩種方法,即開放地址法和分離鏈表法:

  • 開放地址法:若是某一哈希值對應的位置已經被佔用了,就找另外一個沒被佔用的位置。

    1. 開放地址法容易產生堆積問題;不適於大規模的數據存儲
    2. 插入時可能會出現屢次衝突的現象,而刪除時若是元素是多個衝突元素中的一個,須要對後面的元素做處理,實現較複雜
    3. 結點規模很大時會浪費不少空間

    注:關於開放地址法,具體能夠參考這篇文章

  • 分離鏈表法:將散列表的每個單元都擴展成爲一個鏈表,相同哈希值的元素會被存儲在同一個鏈表中。

    1. 分離鏈表法處理衝突簡單,且無堆積現象,平均查找長度短
    2. 鏈表中的結點是動態申請的
    3. 相對開放地址法更加節省空間
    4. 插入與刪除結點比較方便

在jdk8中,使用的就是分離鏈表法,當哈希衝突超過一點的限制,鏈表會轉爲紅黑樹。

2、代碼實現

在這裏咱們實現一個基於分離鏈表法的哈希表:

1.節點類

/**
 * @Author:huang
 * @Date:2020-06-20 10:19
 * @Description:節點
 */
public class Node {

    //節點序號
    int num;

    //下一個節點
    Node next;

    public Node(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Node{" + "num=" + num + '}';
    }
}

2.單鏈表

/**
 * @Author:黃成興
 * @Date:2020-06-20 10:19
 * @Description:單鏈表
 */
public class SingleLinkList {

    private Node head = new Node(0);

    public boolean isEmpty() {
        return head.next == null;
    }

    /**
     * 添加節點到鏈表
     * @param node 要插入的節點
     */
    public void add(Node node) {
        Node temp = head;
        //遍歷鏈表
        while (true) {
            if (temp.next == null) {
                break;
            }
            //不是尾節點就繼續遍歷下一個節點
            temp = temp.next;
        }
        //將尾節點指向即將插入的新節點
        temp.next = node;
    }

    /**
     * 展現鏈表
     */
    public void show() {
        //判斷鏈表是否爲空
        if (isEmpty()) {
            return;
        }
        Node temp = head.next;
        //遍歷鏈表
        while (true) {
            if (temp == null) {
                break;
            }
            System.out.println(temp.toString());
            temp = temp.next;
        }
    }

    /**
     * 根據序號獲取節點
     * @param num 要獲取的節點序號
     * @return
     */
    public Node get(int num){
        //判斷鏈表是否爲空
        if (isEmpty()) {
            return null;
        }
        Node temp = head.next;
        //遍歷鏈表
        while (true) {
            if (temp == null) {
                return null;
            }
            if (temp.num == num) {
                return temp;
            }
            temp = temp.next;
        }
    }

    /**
     * 修改節點
     * @param node 要更新的節點
     */
    public void update(Node node) {
        Node temp = head;
        //判斷鏈表是否爲空
        if (isEmpty()) {
            return;
        }
        //獲取要更新的節點序號
        int nodeNum = node.num;
        //遍歷鏈表
        while (true) {
            //若是已經遍歷完鏈表
            if (temp == null) {
                throw new RuntimeException("編號爲" + temp.num + "的節點不存在!");
            }
            //若是找到了該節點
            if (temp.num == nodeNum) {
                return;
            }
            //繼續遍歷下一節點
            temp = temp.next;
        }
    }

    /**
     * 刪除節點
     * @param num 要刪除的節點編號
     */
    public void delete(int num) {
        Node temp = head;
        //判斷鏈表是否爲空
        if (isEmpty()) {
            return;
        }
        //遍歷鏈表
        while (true) {
            //若是鏈表到底了
            if (temp.next == null) {
                return;
            }
            //若是找到了待刪除節點的前一個節點
            if (temp.next.num == num) {
                //判斷待刪除節點是否爲尾節點
                if (temp.next.next == null){
                    temp.next = null;
                }else {
                    temp.next = temp.next.next;
                }
                return;
            }
            //繼續遍歷下一節點
            temp = temp.next;
        }
    }
}

3.哈希表

/**
 * @Author:黃成興
 * @Date:2020-07-04 11:36
 * @Description:哈希表
 */
public class HashTable {

    //數組長度
    private int size;
    //用於存放數據的數組
    private SingleLinkList[] arr;

    public HashTable(int size) {
        this.size = size;
        //初始化數組
        arr = new SingleLinkList[size];
        //初始化鏈表
        for (int i = 0; i < size; i++) {
            arr[i] = new SingleLinkList();
        }
    }

    /**
     * 獲取哈希值
     * @param item
     * @return
     */
    public int getHashCode(int item) {
        return item % 2;
    }

    /**
     * 插入元素
     * @param item
     */
    public void insert(int item) {
        //獲取哈希值
        int hashCode = getHashCode(item);
        //判斷哈希值是否超過數組範圍
        if (hashCode >= size || hashCode < 0) {
            throw new RuntimeException("哈希值:" + hashCode + "超出初始化長度!");
        }
        //若是該元素在鏈表中不存在就插入
        if (arr[hashCode].isEmpty() || arr[hashCode].get(item) == null) {
            //插入元素
            arr[hashCode].add(new Node(item));
        }else {
            //不然就更新
            arr[hashCode].update(new Node(item));
        }

    }

    /**
     * 查找元素
     * @param item
     */
    public Node get(int item) {
        //獲取哈希值
        int hashCode = getHashCode(item);
        //判斷哈希值是否超過數組範圍
        if (hashCode >= size || hashCode < 0) {
            return null;
        }
        //查找元素
        return arr[hashCode].get(item);
    }

    /**
     * 刪除元素
     * @param item
     */
    public void delete(int item) {
        //獲取哈希值
        int hashCode = getHashCode(item);
        //刪除元素
        arr[hashCode].delete(item);
    }

    /**
     * 展現某個哈希值對應鏈表的所有數據
     * @param item
     */
    public void show(int item) {
        //獲取哈希值
        int hashCode = getHashCode(item);
        arr[hashCode].show();
    }

    /**
     * 展現哈希表的全部數據
     */
    public void showAll() {
        for (int i = 0; i < arr.length; i++) {
            //只展現非空鏈表
            if (!arr[i].isEmpty()) {
                System.out.println("第"+i+"條鏈表:");
                arr[i].show();
            }
        }
    }
}
相關文章
相關標籤/搜索