前言:
顺序结构 以及 平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素时,必须要经过关键码的多次比较 。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( log2 N),搜索的效率 取决于搜索过程中元素的 比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的 存储位置 与它的 关键码 之间能够建立一 一映射的关系,那么在查找时通过该函数可以很快找到该元素。
当向该结构中:
该方式即为 哈希(散列)方法 ,哈希方法中使用的 转换函数 称为 哈希(散列)函数,构造出来的结构 称为 哈希表(HashTable)
(哈希函数的计算:一般是 该元素 % 数组长度)
举个例子:
数据集合: {1,7,6,4,5,9}
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
哈希冲突:不同关键字通过相同哈希函数计算出相同的哈希地址
例如上面的那个例子中:
如果集合中多添加了一个元素:14
此时,元素 4 和 14 所计算出来哈希地址是相同的,那数组中该怎么保存这两个元素,这就是哈希冲突
(哈希表底层数组的容量往往是小于实际要存储的关键字的数量的)
哈希冲突的发生是必然的 - 衣柜的容量是有限的,而衣服是无限的,总会出现哈希冲突的
但是我们可以尽量的 降低冲突率
当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把 key 存放到冲突位置中的“下一个” 空位置中去。
那如何寻找下一个空位置呢?
以这个为例,此时 4 和 14位置冲突
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止
使用另一种数据结构存储冲突元素 (单链表 -> 红黑树)
(具有相同地址的元素归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。)
如果数组中 存储的元素不是 int 类型 怎么办?(Person 对象)
(所有的 Object 类,都有一个方法 int hashCode())
代码实现拉链法:(重点掌握 插入元素的过程!!!)
public class MyHashTable {
//1、数组:
private Node[] array = new Node[11];
//2、维护哈希表中的元素个数
private int size;
//true:key 之前不在哈希表中
//false:key 之前已经在哈希表
public boolean insert(Integer key) {
//1、把对象转成 int 类型
int hashValue = key.hashCode();
//2、把 hashValue 转成合法下标
int index = hashValue % array.length;
//3、遍历 index 位置的链表,确定 key 在不在元素中
Node cur = array[index];
while (cur != null) {
if (key.equals(cur.key)) {
return false;
}
cur = cur.next;
}
//4、把 key 装入结点中,对插入到对应的链表中(JDK 1.8 改成尾插了)
Node node = new Node(key);
node.next = array[index];
array[index] = node;
//5、维护元素个数
size++;
return true;
}
public boolean remove(Integer key) {
int hashValue = key.hashCode();
int index = hashValue % array.length;
Node cur = array[index];//要删除的当前结点
//删除第一个结点
if (key.equals(cur.key)) {
cur.next = null;
return true;
}
Node prev = null;//要删除的结点的前一个结点
while (cur != null) {
if (key.equals(cur.key)) {
if (cur.next != null) {
//删除中间结点
prev.next = cur.next;
} else {
//删除最后一个结点
prev.next = null;
}
size--;
return true;
}
prev = cur;
cur = cur.next;
}
//没找到要删除的元素
return false;
}
public boolean contains(Integer key) {
//找到对应的下标
int hashValue = key.hashCode();
int index = hashValue % array.length;
Node cur = array[index];
while (cur != null) {
if (key.equals(cur.key)) {
return true;
}
cur = cur.next;
}
return false;
}
}
HashMap 和 HashSet 中有关对象时,都需要覆写 equels 和 hashCode 方法
如果不重写 hashCode ,就不能保证两个 index 是相同的(默认的 hashCode 十有八九是不相同的)
如果两个对象想通过 hashMap 找到,要意味两者的 hashCode 必须是相等的