數據結構與算法之哈希表結構

一、概念

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

二、一句話概括

哈希表就是由數組和鏈表二者結合而成的新型數據結構。數組存放key(經過hash計算後生成的存放key的數組位置),鏈表存放value。

三、圖解

在這裏插入圖片描述

hash算法有很多,比如最簡單的哈希算法就是用存放的值去取模數組長度。比如存放元素20,那就是20%5=0,所以20這個元素就存放在數組下標爲0的位置,再比如4%5=4,所以元素4存放在數組下標爲4的位置。
回想下Java的hashmap,典型的key-value,那麼我們put的時候是存放到哪了呢?其實就是key的hash算法得到一個數組下標位置,將其存進去。當然比我說的複雜的多,但是大致原理確是如此。
當我們getKey的時候,首先會根據key進行hash算法得到數組下標位置,查找時間複雜度爲O(1)。

四、哈希碰撞

多個key經過hash算法後得到的是同一個值(同一個數組下標),這就稱之爲哈希碰撞。這時候可以將他們的值都存放在鏈表中。(畢竟鏈表是拉鍊式的,可以往後next next next)

五、Google上機題

Google機試題。
就是讓你存儲員工信息,員工包含id和name,要求能高效率的根據id迅速查找到對應的記錄,題目額外要求不能用mysql、redis、hashmap等存儲介質。

分析:很明顯,這道題是再考我們哈希表結構,看我們是否懂hashmap內部存儲的原理以及結構是怎樣的。
思路:首先要有員工類、員工鏈表類、hashtable類(管理多條鏈表的類,也就是鏈表數組類)。總的思路就是根據員工id進行hash算法定位到存儲的位置,然後將員工類對象信息存儲到上一步定位的位置的鏈表中。

六、coding

package com.chentongwei.struct.hashtable;

import java.util.Scanner;

/**
 * Description:
 *
 * @author TongWei.Chen 2019-12-18 15:48:01
 */
public class HashTableDemo {
    public static void main(String[] args) {
        HashTable hashTable = new HashTable(7);
        String key = "";
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("add:添加僱員");
            System.out.println("list:遍歷僱員");
            System.out.println("find:查找僱員");
            System.out.println("exit:退出系統");

            key = scanner.next();
            switch (key) {
                case "add" :
                    System.out.println("請輸入id");
                    int id = scanner.nextInt();
                    System.out.println("輸入名字");
                    String name = scanner.next();

                    // 創建僱員
                    Emp emp = new Emp(id, name);
                    hashTable.add(emp);
                    break;
                case "list":
                    hashTable.list();
                    break;
                case "find":
                    System.out.println("請輸入id");
                    int findId = scanner.nextInt();
                    hashTable.findEmpById(findId);
                    break;
                case "exit":
                    scanner.close();
                    System.exit(0);
                default:
                    break;
            }
        }
    }
}

// 創建HashTable,管理多條鏈表
class HashTable {
    private EmpLinkedList[] empLinkedListArray;
    // 表示有多少條鏈表
    private int size;

    public HashTable(int size) {
        // 初始化鏈表
        empLinkedListArray = new EmpLinkedList[size];
        this.size = size;
        for (int i = 0; i < empLinkedListArray.length; i ++) {
            empLinkedListArray[i] = new EmpLinkedList();
        }
    }

    // 添加僱員
    public void add (Emp emp) {
        // 根據員工id,得到該員工應該添加到哪條鏈表上
        int empLinkedListNo = hash(emp.id);
        // 將emp加入到對應的鏈表中
        empLinkedListArray[empLinkedListNo].add(emp);
    }

    // 遍歷所有鏈表,遍歷hash表
    public void list () {
        for (int i = 0; i < size; i ++) {
            empLinkedListArray[i].list(i);
        }
    }

    // 輸入id查找emp
    public void findEmpById(int no) {
        int hash = hash(no);
        Emp emp = empLinkedListArray[hash].findEmpById(no);
        if (emp != null) {
            System.out.printf("在第%d條鏈表中找到該僱員, id = %d\n", (hash + 1), no);
        } else {
            System.out.println("再哈希表中沒找到該僱員");
        }
    }

    // 編寫一個散列函數,採取最簡單的取模法
    public int hash (int id) {
        return id % size;
    }
}

// 創建鏈表
class EmpLinkedList {
    // 頭指針,指向第一個Emp,因此我們這個鏈表的head是直接指向第一個Emp
    private Emp head;

    // 添加僱員到鏈表
    // 說明:
    // 1.假定添加僱員就直接往鏈表後面追加,不考慮按照僱員id排序
    // 2.因此我們將該僱員直接加入到本鏈表的最後一個即可
    public void add (Emp emp) {
        if (head == null) {
            head = emp;
            return;
        }
        // 輔助指針幫助我們定位到鏈表最後
        Emp tmp = head;
        while (true) {
            if (tmp.next == null) {
                break;
            }
            //  後移一位,直到最後
            tmp = tmp.next;
        }
        // 加入到鏈表最後
        tmp.next = emp;
    }

    // 遍歷鏈表的僱員信息
    public void list (int no) {
        if (head == null) {
            System.out.println("第" + (no + 1) + "條鏈表爲空");
            return;
        }
        Emp tmp = head;
        System.out.print("第" + (no + 1) + "條鏈表的信息爲:");
        while (true) {
            System.out.printf("=> id = %d name=%s\t", tmp.id, tmp.name);
            if (tmp.next == null) {
                break;
            }
            tmp = tmp.next;
        }
        System.out.println();
    }

	// 根據id查找員工信息
    public Emp findEmpById (int id) {
        // 鏈表爲空
        if (head == null) {
            System.out.println("鏈表爲空");
            return null;
        }
        // 創建輔助指針
        Emp tmp = head;
        while (true) {
        	// 若找到了則break
            if (tmp.id == id) {
                break;
            }
            // 代表遍歷完當前鏈表沒找到該僱員
            if (tmp.next == null) {
                tmp = null;
                break;
            }
            tmp = tmp.next;
        }
        return tmp;
    }

}
// 充當鏈表中的Node節點的作用
class Emp {
    public int id;
    public String name;
    public Emp next;

    public Emp(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

結果
add:添加僱員
list:遍歷僱員
find:查找僱員
exit:退出系統
add
請輸入id
0
輸入名字
tom
add:添加僱員
list:遍歷僱員
find:查找僱員
exit:退出系統
add
請輸入id
1
輸入名字
jim
add:添加僱員
list:遍歷僱員
find:查找僱員
exit:退出系統
add
請輸入id
2
輸入名字
pom
add:添加僱員
list:遍歷僱員
find:查找僱員
exit:退出系統
add
請輸入id
3
輸入名字
angle
add:添加僱員
list:遍歷僱員
find:查找僱員
exit:退出系統
add
請輸入id
5
輸入名字
haha
add:添加僱員
list:遍歷僱員
find:查找僱員
exit:退出系統
list
第1條鏈表的信息爲:=> id = 0 name=tom => id = 5 name=haha
第2條鏈表的信息爲:=> id = 1 name=jim
第3條鏈表的信息爲:=> id = 2 name=pom
第4條鏈表的信息爲:=> id = 3 name=angle
第5條鏈表爲空
add:添加僱員
list:遍歷僱員
find:查找僱員
exit:退出系統
find
請輸入id
5
在第1條鏈表中找到該僱員, id = 5
add:添加僱員
list:遍歷僱員
find:查找僱員
exit:退出系統

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章