五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Java三十二篇: 哈希表

2023-03-10 01:31 作者:小劉Java之路  | 我要投稿


平安春運(yùn)


1.概念

哈希表(Hash table,也叫散列表):

是根據(jù)關(guān)鍵碼值(Key value)而直接進(jìn)行訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)。也就是說(shuō),它通過(guò)把關(guān)鍵碼值映射到表中一個(gè)位置來(lái)訪問(wèn)記錄,以加快查找的速度。這個(gè)映射函數(shù)叫做散列函數(shù),存放記錄的數(shù)組叫做散列表。

給定表M,存在函數(shù)f(key),對(duì)任意給定的關(guān)鍵字值key,代入函數(shù)后若能得到包含該關(guān)鍵字的記錄在表中的地址,則稱表M為哈希(Hash)表,函數(shù)f(key)為哈希(Hash) 函數(shù)。

哈希表的本質(zhì)上上一個(gè)數(shù)組(元素是Entry)

這里的 id 是個(gè)key,哈希表就是根據(jù)key值來(lái)通過(guò)哈希函數(shù)計(jì)算得到一個(gè)值,這個(gè)值就是用來(lái)確定這個(gè)Entry要存放在哈希表中的位置的,實(shí)際上這個(gè)值就是一個(gè)下標(biāo)值,來(lái)確定放在數(shù)組的哪個(gè)位置上

比如這里的 id 是001,那么經(jīng)過(guò)哈希函數(shù)的計(jì)算之后得到了1,這個(gè)1就是告訴我們應(yīng)該把這個(gè)Entry放到哪個(gè)位置,這個(gè)1就是數(shù)組的確切位置的下標(biāo),也就是需要放在數(shù)組中下表為1的位置

img

Hash Table的查詢速度非常的快,幾乎是O(1)的時(shí)間復(fù)雜度

hash就是找到一種數(shù)據(jù)內(nèi)容和數(shù)據(jù)存放地址之間的映射關(guān)系

散列法:元素特征轉(zhuǎn)變?yōu)閿?shù)組下標(biāo)的方法

優(yōu)點(diǎn):

不論哈希表中有多少數(shù)據(jù),查找、插入、刪除(有時(shí)包括刪除)時(shí)間接近常量的時(shí)間即0(1)的時(shí)間級(jí)

缺點(diǎn):

它是基于數(shù)組的,數(shù)組創(chuàng)建后難于擴(kuò)展,某些哈希表被基本填滿時(shí),性能下降得非常嚴(yán)重,所以程序員必須要清楚表中將要存儲(chǔ)多少數(shù)據(jù)

2.哈希函數(shù)的構(gòu)造方法

2.1 直接定制法(不常用)

取關(guān)鍵字或關(guān)鍵字的某個(gè)線性函數(shù)值為哈希地址 即, H(key) = key H(key) = a * key + b

優(yōu)點(diǎn):簡(jiǎn)單,均勻,不會(huì)產(chǎn)生沖突 缺點(diǎn):需要實(shí)現(xiàn)直到關(guān)鍵字的分布情況,適合查找表比較小且連續(xù)的情況

2.2 數(shù)字分析法

數(shù)字分析法用于處理關(guān)鍵字是位數(shù)比較多的數(shù)字,通過(guò)抽取關(guān)鍵字的一部分進(jìn)行操作,計(jì)算哈希存儲(chǔ)位置的方法

例:

身份證號(hào)是有規(guī)律的,現(xiàn)在要存儲(chǔ)一個(gè)班級(jí)學(xué)生的身份證號(hào)碼,假設(shè)這個(gè)班級(jí)的學(xué)生都出生在同一個(gè)地區(qū),同一年,那么他們的身份證的前面數(shù)位都是相同的,那么我們可以截取后面不同的幾位存儲(chǔ),假設(shè)有5位不同,那么就用這五位代表地址

適用場(chǎng)景:

處理關(guān)鍵字位數(shù)比較大的情況,事先知道關(guān)鍵字的分布且關(guān)鍵字的若干位分布均勻


2.3 平方取中法

先對(duì)關(guān)鍵字取平方,然后選取中間幾位為哈希地址,取的位數(shù)由表長(zhǎng)決定

例:key=1234 1234^2=1522756 取227作hash地址 key=4321 4321^2=18671041 取671作hash地址

適用場(chǎng)景:不知道關(guān)鍵字的分布,而位數(shù)又不是很大的情況

2.4 折疊法

如果數(shù)字的位數(shù)很多,可以將數(shù)字分割為幾個(gè)部分,取他們的疊加和作為hash地址

例:key=123 456 789 可以存儲(chǔ)在61524,取末三位,存在524的位置

適用場(chǎng)景:關(guān)鍵字位數(shù)很多,而且關(guān)鍵字每一位上數(shù)字分布大致均勻

2.5 除留余數(shù)法(用的較多)

H(key)=key MOD p (p<=m m為表長(zhǎng))

例:存儲(chǔ)3 6 9,那么p就不能取3 因?yàn)?3 MOD 3 == 6 MOD 3 == 9 MOD 3,地址沖突

一般來(lái)說(shuō),p應(yīng)為不大于m的質(zhì)數(shù)或是不含20以下的質(zhì)因子的合數(shù),這樣可以減少地址的重復(fù)(沖突)

2.6 隨機(jī)數(shù)法

選擇一個(gè)隨機(jī)數(shù),取關(guān)鍵字的隨機(jī)函數(shù)值作為他的哈希地址 即,f(key) = random (key)

適合場(chǎng)景:關(guān)鍵字的長(zhǎng)度不等時(shí)。當(dāng)遇到特殊字符的關(guān)鍵字時(shí),需要將其轉(zhuǎn)換為某種數(shù)字

3.哈希沖突及解決方式

哈希沖突圖例:

img

3.1 開放尋址法

H(key)的哈希函數(shù):

H(key1)= H(keyi)

那么 keyi 存儲(chǔ)位置 Hi = (H(key)+di) MOD m ,m為表長(zhǎng)

di 有三種取法:

線性探測(cè)再散列: di = c * i

平方探測(cè)再散列:di = 1^2 , -1 ^2,2 ^2,-2 ^2…

隨機(jī)探測(cè)再散列(雙探測(cè)再散列):di是一組偽隨機(jī)數(shù)列

簡(jiǎn)單來(lái)說(shuō)就是,既然位置被占了,那就另外再找個(gè)位置,怎么找其他的位置呢?這里其實(shí)也有很多的實(shí)現(xiàn),我們說(shuō)個(gè)最基本的就是既然當(dāng)前位置被占用了,我們就看看該位置的后一個(gè)位置是否可用,也就是1的位置被占用了,我們就看看2的位置,如果沒(méi)有被占用,那就放到這里唄,當(dāng)然,也有可能2的位置也被占用了,那咱就繼續(xù)往下找,看看3的位置,一次類推,直到找到空位置

3.2 鏈地址法

存儲(chǔ)數(shù)據(jù)后面加一個(gè)指針,指向后面沖突的數(shù)據(jù)

比如:

img

簡(jiǎn)單來(lái)說(shuō),Entry還額外的保存了一個(gè)next指針,這個(gè)指針指向數(shù)組外的另外一個(gè)位置,將李四安排在這里,然后張三那個(gè)Entry中的next指針就指向李四的這個(gè)位置,也就是保存的這個(gè)位置的內(nèi)存地址,如果還有沖突,那就把又沖突的那個(gè)Entry放在一個(gè)新位置上,然后李四的Entry中的next指向它,這樣就形成了一個(gè)鏈表。


img

3.3 再哈希法

Hi = RHi(key) i = 1,2,…,k RHi均是不同的哈希函數(shù),意思為:當(dāng)繁盛沖突時(shí),使用不同的哈希函數(shù)計(jì)算地址,直到不沖突為止。這種方法不易產(chǎn)生堆積,但是耗費(fèi)時(shí)間

3.4 公共溢出區(qū)法

即設(shè)立兩個(gè)表:基礎(chǔ)表和溢出表。將所有關(guān)鍵字通過(guò)哈希函數(shù)計(jì)算出相應(yīng)的地址。然后將未發(fā)生沖突的關(guān)鍵字放入相應(yīng)的基礎(chǔ)表中,一旦發(fā)生沖突,就將其依次放入溢出表中即可。

在查找時(shí),先用給定值通過(guò)哈希函數(shù)計(jì)算出相應(yīng)的散列地址后,首先與基本表的相應(yīng)位置進(jìn)行比較,如果不相等,再到溢出表中順序查找。

此種方法適用于數(shù)據(jù)和沖突較少的情況



4.擴(kuò)容

增長(zhǎng)因子,也叫作負(fù)載因子,簡(jiǎn)單點(diǎn)說(shuō)就是已經(jīng)被占的位置與總位置的一個(gè)百分比。

比如負(fù)載因子是0.7時(shí),一共十個(gè)位置,現(xiàn)在已經(jīng)占了七個(gè)位置,就觸發(fā)了擴(kuò)容機(jī)制,也就是達(dá)到了總位置的百分之七十就需要擴(kuò)容。

簡(jiǎn)單來(lái)說(shuō),擴(kuò)容就是新創(chuàng)建一個(gè)數(shù)組是原來(lái)的2倍,然后把原數(shù)組的所有Entry都重新Hash一遍放到新的數(shù)組。

數(shù)組擴(kuò)大了,所以一般哈希函數(shù)也會(huì)有變化,這里的Hash也就是把之前的數(shù)據(jù)通過(guò)新的哈希函數(shù)計(jì)算出新的位置來(lái)存放



代碼的實(shí)際需求

  • 看一個(gè)實(shí)際需求, google 公司的一個(gè)上機(jī)題:

  • 有一個(gè)公司,當(dāng)有新的員工來(lái)報(bào)道時(shí),要求將該員工的信息加入(id, 性別, 年齡, 住址…),當(dāng)輸入該員工的 id 時(shí),要求查找到該員工的所有信息

  • 要求:不使用數(shù)據(jù)庫(kù),盡量節(jié)省內(nèi)存,速度越快越好 => 哈希表(散列)

哈希表編程思路:


    • 先根據(jù)對(duì)象的信息將其散列,得到 hashCode

    • 根據(jù)對(duì)象的 hashCode 值,找到對(duì)應(yīng)的數(shù)組下標(biāo),其實(shí)就是找到存儲(chǔ)對(duì)象的鏈表


    • 在鏈表中進(jìn)行相應(yīng)的增刪改查操作

代碼實(shí)現(xiàn)

Emp 節(jié)點(diǎn)

//表示一個(gè)雇員
class EmpNode {
publicint id;
public String name;
public EmpNode next; // next 默認(rèn)為 null

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

Emp 鏈表

  • head 是首指針(指向真正存放數(shù)據(jù)的節(jié)點(diǎn)),不是頭指針

//創(chuàng)建EmpLinkedList ,表示鏈表
class EmpLinkedList {
// 首指針,指向第一個(gè)EmpNode,因此我們這個(gè)鏈表的head 是直接指向第一個(gè)EmpNode
private EmpNode head; // 默認(rèn)null

// 添加雇員到鏈表
// 說(shuō)明
// 1. 假定,當(dāng)添加雇員時(shí),id 是自增長(zhǎng),即id的分配總是從小到大
// 因此我們將該雇員直接加入到本鏈表的最后即可
public void add(EmpNode empNode) {
// 如果是添加第一個(gè)雇員
if (head == null) {
head = empNode;
return;
}
// 如果不是第一個(gè)雇員,則使用一個(gè)輔助的指針,幫助定位到最后
EmpNode curEmp = head;
while (true) {
if (curEmp.next == null) {// 說(shuō)明到鏈表最后
break;
}
curEmp = curEmp.next; // 后移
}
// 退出時(shí)直接將emp 加入鏈表
curEmp.next = empNode;
}

// 遍歷鏈表的雇員信息
public void list(int no) {
if (head == null) { // 說(shuō)明鏈表為空
System.out.println("第 " + (no + 1) + " 鏈表為空");
return;
}
System.out.print("第 " + (no + 1) + " 鏈表的信息為");
EmpNode curEmp = head; // 輔助指針
while (true) {
System.out.printf(" => id=%d name=%s ", curEmp.id, curEmp.name);
if (curEmp.next == null) {// 說(shuō)明curEmp已經(jīng)是最后結(jié)點(diǎn)
break;
}
curEmp = curEmp.next; // 后移,遍歷
}
System.out.println();
}

// 根據(jù)id查找雇員
// 如果查找到,就返回Emp, 如果沒(méi)有找到,就返回null
public EmpNode findEmpById(int id) {
// 判斷鏈表是否為空
if (head == null) {
System.out.println("鏈表為空");
returnnull;
}
// 輔助指針
EmpNode curEmp = head;
while (true) {
if (curEmp.id == id) {// 找到
break;// 這時(shí)curEmp就指向要查找的雇員
}
curEmp = curEmp.next;// 后移
// 退出
if (curEmp == null) {// 說(shuō)明遍歷當(dāng)前鏈表沒(méi)有找到該雇員
break;
}
}

return curEmp;
}

}

Emp 哈希表

//創(chuàng)建HashTab 管理多條鏈表
class HashTab {
private EmpLinkedList[] empLinkedListArray;
privateint size; // 表示有多少條鏈表

// 構(gòu)造器
public HashTab(int size) {
this.size = size;
// 初始化empLinkedListArray
empLinkedListArray = new EmpLinkedList[size];
for (int i = 0; i < size; i++) {
empLinkedListArray[i] = new EmpLinkedList();
}
}

// 添加雇員
public void add(EmpNode empNode) {
// 根據(jù)員工的id ,得到該員工應(yīng)當(dāng)添加到哪條鏈表
int empLinkedListNO = hashFun(empNode.id);
// 將emp 添加到對(duì)應(yīng)的鏈表中
empLinkedListArray[empLinkedListNO].add(empNode);
}

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

// 根據(jù)輸入的id,查找雇員
public void findEmpById(int id) {
// 使用散列函數(shù)確定到哪條鏈表查找
int empLinkedListNO = hashFun(id);
EmpNode empNode = empLinkedListArray[empLinkedListNO].findEmpById(id);
if (empNode != null) {// 找到
System.out.printf("在第%d條鏈表中找到 雇員 id = %d\n", (empLinkedListNO + 1), id);
} else {
System.out.println("在哈希表中,沒(méi)有找到該雇員~");
}
}

// 編寫散列函數(shù), 使用一個(gè)簡(jiǎn)單取模法
public int hashFun(int id) {
return id % size;
}

}

代碼測(cè)試

publicclass HashTabDemo {

public static void main(String[] args) {

// 創(chuàng)建哈希表
HashTab hashTab = new HashTab(7);

// 寫一個(gè)簡(jiǎn)單的菜單
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: 退出系統(tǒng)");
System.out.println();
key = scanner.next();
switch (key) {
case"add":
System.out.println("輸入id");
int id = scanner.nextInt();
System.out.println("輸入名字");
String name = scanner.next();
// 創(chuàng)建 雇員
EmpNode empNode = new EmpNode(id, name);
hashTab.add(empNode);
break;
case"list":
hashTab.list();
break;
case"find":
System.out.println("請(qǐng)輸入要查找的id");
id = scanner.nextInt();
hashTab.findEmpById(id);
break;
case"exit":
scanner.close();
System.exit(0);
default:
break;
}
}

}

}

  • 程序運(yùn)行結(jié)果

add: ?添加雇員
list: 顯示雇員
find: 查找雇員
exit: 退出系統(tǒng)

add
輸入id
1
輸入名字
Heygo
add: ?添加雇員
list: 顯示雇員
find: 查找雇員
exit: 退出系統(tǒng)

list
第 1 鏈表為空
第 2 鏈表的信息為 => id=1 name=Heygo => id=8 name=NiuNiu
第 3 鏈表的信息為 => id=2 name=Oneby
第 4 鏈表為空
第 5 鏈表為空
第 6 鏈表為空
第 7 鏈表為空
add: ?添加雇員
list: 顯示雇員
find: 查找雇員
exit: 退出系統(tǒng)

find
請(qǐng)輸入要查找的id
9
在哈希表中,沒(méi)有找到該雇員~
add: ?添加雇員
list: 顯示雇員
find: 查找雇員
exit: 退出系統(tǒng)

總結(jié):

哈希表基于數(shù)組,類似于key-value的存儲(chǔ)形式,關(guān)鍵字值通過(guò)哈希函數(shù)映射為數(shù)組的下標(biāo),如果一個(gè)關(guān)鍵字哈?;揭颜加玫臄?shù)組單元,這種情況稱為沖突。用來(lái)解決沖突的有兩種方法:開放地址法和鏈地址法。在開發(fā)地址法中,把沖突的數(shù)據(jù)項(xiàng)放在數(shù)組的其它位置;在鏈地址法中,每個(gè)單元都包含一個(gè)鏈表,把所有映射到同一數(shù)組下標(biāo)的數(shù)據(jù)項(xiàng)都插入到這個(gè)鏈表中。

img

?



Java三十二篇: 哈希表的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
读书| 临城县| 大荔县| 石棉县| 芒康县| 孙吴县| 台北市| 永平县| 和林格尔县| 通辽市| 合作市| 莆田市| 齐齐哈尔市| 和田市| 汝城县| 秦皇岛市| 广平县| 卫辉市| 紫阳县| 贺州市| 武胜县| 长阳| 景德镇市| 连南| 大石桥市| 高雄市| 新乐市| 河南省| 丘北县| 峡江县| 上思县| 上蔡县| 波密县| 怀柔区| 连南| 绥宁县| 永城市| 固始县| 广水市| 广德县| 合肥市|