目录
1,什么是哈希表
哈希表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
- 下面总结一下什么是哈希表
- 哈希表是一种数据结构
- 哈希表表示了关键码值和记录的映射关系
- 哈希表可以加快查找速度
- 任意哈希表,都满足有哈希函数f(key),代入任意key值都可以获取包含该key值的记录在表中的地址
- 语言太抽象,我们可以通过下面这张图了解到底什么是哈希表
看上面的图很好理解,数组就是我们的哈希表实现,在一个典型的哈希表实现中,我们将数组总长度设为模数,将key值直接对其取模,所得的值为数组下标,也就是我们的元素存储在哈希表中的位置下标,但是入过数组中这个索引出有元素,那么其他元素如果映射到这里的话就会发生冲突,该如何存储元素呢?这就是我接下来要讲的冲突处理方法。
1.1,如何解决哈希碰撞问题
常用的解决方案有散列法和拉链法。散列法又分为开放寻址法和再散列法等,此处不做展开。java中使用的实现为拉链法,即:在每个冲突处构建链表,将所有冲突值链入链表,如同拉链一般一个元素扣一个元素,故名拉链法。
需要注意的是,如果遭到恶意哈希碰撞攻击,拉链法会导致哈希表退化为链表,即所有元素都被存储在同一个节点的链表中,此时哈希表的查找速度=链表遍历查找速度=O(n)。
1.2,哈希表有什么优势呢?
通过前面的概念了解,哈希表的优点呼之欲出:通过关键值计算直接获取目标位置,对于海量数据中的精确查找有非常惊人的速度提升,理论上即使有无限的数据量,一个实现良好的哈希表依旧可以保持O(1)的查找速度,而O(n)的普通列表此时已经无法正常执行查找操作(实际上不可能,受到JVM可用内存限制,机器内存限制等)。
2,链表的实现
2.1,哈希表中的节点类型
class HashNode{
public int id;
public HashNode next;//指向下一个节点
public HashNode(int id) {
this.id = id;
}
public int getId() {
return id;
}
@Override
public String toString() {
return "HashNode{" +
"id=" + id +
'}';
}
}
2.2,链表的定义
2.2.1,链表的节点定义
class HashLinked {
// 头指针,指向第一个元素,也就是说链表没有头结点
// 什么叫做没有头指针,也就是说head指针永远指向第一个元素节点
private HashNode head;//默认是空的
}
2.2.2,向链表末尾添加一个元素
/**
* 向链表中添加一个节点,默认使用尾插法
* @param hashNode 需要添加的节点
*/
public void add(HashNode hashNode) {
if(head == null){
// 说明添加的是第一个节点
head=hashNode;//链表没有头结点
return;
}
// 否则就添加到链表的末尾
HashNode curNode=head;
// 第一种写法
while (curNode.next != null){
curNode=curNode.next;
}
curNode.next=hashNode;
// 第二种写法,错误写法,因为curNode为空时,其下一个指针域为null,会包空指针异常
// 要理解什么是空指针异常,当当前节点为null的时候,其next域会报空指针异常,但是
// 只要最后一个节点不是空,即使其next为null,也不会报空指针异常
// 也就是说只有null会报空指针异常
// while (curNode != null){
// curNode=curNode.next;//指向下一个节点后,下一个节点是null,所以回报空指针异常
// if(curNode == null)
// curNode.next=hashNode;
// }
}
2.2.3,在链表中查找一个元素
/**
* 在链表中的查找操作
* @param id 元素的id值
* @return 查找成功就返回元素,否则就返回null
*/
public HashNode find(int id){
if(this.head == null){
return null;//如果当前的链表是空,就返回null
}else {
// 也就是当前的链表不是空的,遍历链表查找元素
HashNode curNode=head;
while (curNode!= null){
if(curNode.getId() == id)
return curNode;
else
{
curNode=curNode.next;
}
}
}
//退出循环,说明没有找到该元素,直接返回null
return null;
}
2.2.4,在链表中删除一个元素
/**
* 在链表中删除一个元素
* @param id 需要删除元素的id
* @return 如果删除成功就返回true,否则就直接返回false
*/
public boolean delete(int id){
if(this.head == null){
return false;
}else {
// 查找要删除的节点
HashNode curNode=find(id);
HashNode preNode=head;
if(curNode != null){
if(preNode.getId() == id){
head=head.next;
return true;
}else if(preNode.next != null){
while (preNode.next != curNode){
preNode=preNode.next;
}
preNode.next=curNode.next;
return true;
}else {
return false;
}
}else {
// 如果链表中没有要删除的元素,就直接退出
return false;
}
}
}
2.2.5,打印链表
// 打印链表
public void print(int i){
if(this.head == null){
// 说明当前的莲表是空链表
System.out.println("第"+(i)+"条链表是空的!");
}
else {
HashNode curNode=head;
System.out.print("第"+(i)+"条链表的元素是:");
while (curNode != null){
System.out.print(curNode.getId()+" ");
curNode=curNode.next;
}
System.out.println();
}
}
3,哈希表的实现
3.1,定义一个哈希表
//现在创建我们的哈希表,用来管理多条链表
class HashTable{
private HashLinked []HashArrayList;
private int size;//标示哈希表的大小,也就是链表的条数
// 构造函数,初始化数组的大小
public HashTable(int size) {
this.size=size;
// 这里只是对整条数组进行初始化
HashArrayList = new HashLinked[size];
// 在这里对每一条链表进行初始化,这里非常重要
// 因为数组中每一个元素都是一个链表,所以每一个链表都要初始化
for(int j=0;j<this.size;j++){
HashArrayList[j]=new HashLinked();
}
}
}
3.2,向哈希表中添加一个元素
/**
* 向哈希表中添加一个元素
* @param hashNode 添加的元素节点
*/
public void addEle(HashNode hashNode){
//获取元素存储的哈希值
int getLinkedNum=getHash(hashNode.id);
// 把此节点添加到对应的链表中
// 在这里会报空指针异常,因为只是创建数组,被默认初始化为null,所以报错
// 解决方法,分别初始化每一条链表
HashArrayList[getLinkedNum].add(hashNode);
}
3.3,获取元素索引
/**
* 编写一个哈希函数,此函数可以根据id获取其存储的位置
* @param id 元素存储的id
* @return 返回元素在哈希表中的存储位置索引
*/
public int getHash(int id){
return id%this.size;
}
3.4,哈希表的遍历
/**
* 哈希表的遍历操作
*/
public void printHashTable(){
for(int i=0;i<this.size;i++){
HashArrayList[i].print(i);
}
}
3.5,在哈希表中查找一个元素
/**
* 哈希表的查找算法
* @param id 根据id查找元素的值
* @return 如果查找到,就返回元素,否则就返回空
*/
public HashNode findEleById(int id){
// 首先要根据id值去获取元素的hash码
int EleHash=getHash(id);
// 根据hash去查找看元素在那一条链表上面
return HashArrayList[EleHash].find(id);
}
3.6,在哈希表中删除一个元素
/**
* 在哈希表中删除一个元素
* @param id 需要删除元素的di
* @return 如果删除成功,就返回true,否则就返回false
*/
public boolean deleteEleInHashTable(int id){
int hash=getHash(id);
return HashArrayList[hash].delete(id);
}
4,代码测试
public class HashTableDemo {
public static void main(String[] args) {
HashTable hashTable=new HashTable(5);
hashTable.addEle(new HashNode(7));
hashTable.addEle(new HashNode(58));
hashTable.addEle(new HashNode(98));
hashTable.addEle(new HashNode(41));
hashTable.addEle(new HashNode(25));
hashTable.addEle(new HashNode(36));
hashTable.addEle(new HashNode(55));
hashTable.addEle(new HashNode(78));
hashTable.printHashTable();
ArrayList arrayList=new ArrayList();
// 在哈希表里面查找操作
HashNode hashNode=hashTable.findEleById(6666);
System.out.println(hashNode);
// 从哈希表中删除一个元素
boolean b=hashTable.deleteEleInHashTable(78);
System.out.println(b);
hashTable.printHashTable();
}
}
- 测试结果