哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。哈希表基于数组。
优缺点
1.无论哈希表中多少数据,插入只需要接近常量的时间,时间复杂度为O(1)
2.基于数组的,数组创建后难以扩展,哈希表被填满后,性能下降
3.没有一种简便的方法可以以任意的顺序遍历数据项,所以适用于如果不需要有序遍历数据,并且可以预估数据量
已填入哈希表的数据项和表长的比率叫做装填因子
哈希表重要的是如何把关键字转换为数组下标,有时候会hash冲突(哈希化之后数组下标相同);解决hash冲突的方法
开放地址法(再寻找一个空位解决冲突)
若数据不能直接存放通过hash函数计算的下标对应的单元,需要数组的其他位置来存放数据
1.线性探测
通俗来说线性探测就是如果X是要插入的位置,它已经被占用,那就使用X+1,X+2...以此类推;直到找到空位
线性探测会发生数据聚集(一连串的已填充序列越来越长);聚集越严重;探测长度越长,存取序列最后单元会非常耗时
2.二次探测
二次探测是防止聚集产生的一种尝试,思路是探测相隔较远的单元,而不是相邻的位置
线性探测中X+1,X+2,X+3..
二次探测是步数的平方
X+1,X+4,X+9,X+16,X+25...
二次探测会发生二次聚集,原因同理
3.再哈希法
使用不同的哈希函数再做一遍哈希,要求哈希结果不能为0(否则每次都在原地踏步)
链地址法(在每个单元设置一个链表)
链地址图示
Java代码(链地址法)
//模拟实现hash表
class StudentHashTable {
//存放链表的数组
private HashLinkedList[] stuLinkedList;
//数组大小
private int size;
//初始化
public StudentHashTable(int size) {
this.size = size;
stuLinkedList = new HashLinkedList[size];
//链表初始化,否则执行变更操作会有空指针异常
for (int i = 0; i < size; i++) {
stuLinkedList[i] = new HashLinkedList();
}
}
//加入一个元素
public void add(Student student) {
//首先判断新元素落在哪一个链表
int stuLinkedListNO = hashFun(student.id);
stuLinkedList[stuLinkedListNO].add(student);
}
//根据学生id查找
public void findStudentById(int id) {
//首先判断在哪一个链表
int stuLinkedListNO = hashFun(id);
Student student = stuLinkedList[stuLinkedListNO].findStudentById(id);
if (student != null) {
System.out.printf("该学生在第%d条链表 【id=%d,name=%s】", (stuLinkedListNO + 1), student.id, student.name
);
} else {
System.out.println("查找-哈希表未找到该学生");
}
}
//删除一个元素
public void delStudentById(int id) {
//首先判断在哪一个链表
int stuLinkedListNO = hashFun(id);
//根据返回结果判断是否删除 以及删除原因 -1代表没有找到
int result = stuLinkedList[stuLinkedListNO].delStudentBuId(id);
if (result == -1) {
System.out.println("删除-哈希表未找到该学生");
} else {
System.out.println("删除成功");
}
}
//遍历哈希表
public void list() {
for (int i = 0; i < size; i++) {
stuLinkedList[i].list(i);
}
}
//哈希函数采用简单的取模
public int hashFun(int no) {
return no % size;
}
}
//创建一个没有头节点的单链表
class HashLinkedList {
private Student head;//链表第一个元素
private Student curStudent;//定位链表最后一个元素
public void add(Student student) {
if (head == null) {
head = student;
curStudent = student;
return;
}
curStudent.next = student;
curStudent = student;
}
public void list(int no) {
if (head == null) {
System.out.printf("第%d条的链表为空", (no + 1));
System.out.println();
return;
}
Student tmp = head;
System.out.printf("第%d条的链表信息:", (no + 1));
while (true) {
System.out.printf("=>id=%d name=%s", tmp.id, tmp.name);
if (tmp.next == null) {
break;
}
tmp = tmp.next;
}
System.out.println();
}
//根据id删除一个
public int delStudentBuId(int id) {
//先判断有没有这个学生
Student student = findStudentById(id);
if (student == null) {
return -1;
}
//判断该链表是否就一个元素且该元素就是要找的学生
if (head.next == null) {
head = null;
return 0;
}
//上述都不是就遍历查找
Student tmp = head;
while (true) {
if (tmp.next.id == id) {
tmp.next = tmp.next.next;
break;
}
tmp = tmp.next;
}
return 0;
}
public Student findStudentById(int id) {
if (head == null) {
return null;
}
Student tmp = head;
while (true) {
if (tmp.id == id) {
break;
}
tmp = tmp.next;
}
return tmp;
}
}
//学生节点
class Student {
public int id;
public String name;
public Student next;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}