哈希表
一、概念
在顺序结构和平衡树中,元素与其存储下标没有直接的对应关系,每查找一个元素时,必须要遍历这个结构进行多次比较才能找到这个元素,而现在我们想不经过任何比较,一次就从表中查找到这个元素,通过某些方法实现的这种存储结构就是哈希表。
二、基本思想
在哈希表中发通过某种函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
在哈希表中主要进行两种操作:
1.插入元素
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
2.搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。
三、结构冲突
从上边我们可以了解到应用函数可以很快地查找元素,但同时也会暴露出新的问题,要知道不同的数执行同一个函数可能会得到同一个结果,那么对出现这种函数值相同的元素的现象我们就称之为冲突,因此上解决冲突就成了我们实现哈希表最大的问题。
四、负载因子
散列表的负载因子定义为:α=填入表中的元素个数 / 散列表的长度。
α是散列表装满程度的标志因子,由于表长是定值,α与填入表中的元素个数成正比,所以,α越大,填入表中的元素就越多,产生冲突的可能性就越大;反之,α越小,标明填入表中的元素就越少,产生冲突的可能性就越小。一般应该严格控制在0.7~0.8之间。超过0.8,查表时的不命中率按照指数曲线上升。
负载因子和冲突率的粗略演示:
所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率。
已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。
五、开散列/哈希桶解决冲突实现哈希表
解决哈希冲突有几种方法,最常见的方法就是开散列法。
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
通俗的说就是先通过函数计算元素在哈下表中的存储位置,再将同一存储位置的元素用链表连接起来。
开散列实现HashMap()
1.get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
class Node{
Node next;
int key;
int value;
public Node(int key, int value){
this.key = key;
this.value = value;
next = null;
}
}
public class HashBucket {
private Node[] arr = new Node[10];
private int size = 0;
private static final double LOAD_FACTORY = 0.7;//负载因子最大值
public double loadFactory(){//计算负载因子
return size * 1.0 / arr.length;
}
public int get(int key){//查询
int index = key % arr.length;//在哈希表中查找对应的下标
Node cur = arr[index];//此下标处的头节点
while(cur != null){//遍历此处链表
if(cur.key == key){//找到返回对应的value
return cur.value;
}
cur = cur.next;
}
return -1;//没找到返回-1
}
}
2.put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
public int put(int key, int value){//插入
if(loadFactory() >= LOAD_FACTORY){//负载因子过大扩容调整
resize();
}
int index = key % arr.length;//计算哈希值
Node cur = arr[index];//此处头节点
while(cur != null){//遍历此处的链表,确定此时的map中没有插入的值
if(cur.key == key){//不能插入相同的值
int oldValue = cur.value;
cur.value = value;
return oldValue;
}
cur = cur.next;
}
//头插
Node node = new Node(key, value);
node.next = arr[index];
arr[index] = node;
size++;
return -1;
}
public void resize(){//扩容
Node[] newArray = new Node[arr.length * 2];
for(int i = 0; i < arr.length; i++){//遍历原数组,将原数组中的数据全部拷贝到新数组中
Node cur = arr[i];//头节点
while(cur != null){//遍历此下标处的链表
Node next = cur.next;
int index = cur.key % newArray.length;//新表中应该插入的位置
cur.next = newArray[index];//进行头插
newArray[index] = cur;
cur = next;
}
}
arr = newArray;
}
3.remove(key):如果映射中存在这个键,删除这个数值对。
public int remove(int key){//删除
int index = key % arr.length;//计算哈希值
Node cur = arr[index];//此处的头节点
Node prev = null;
while(cur != null){//遍历链表
if(cur.key == key){//找到应该删除的节点
if(prev == null){//头删
arr[index] = cur.next;
}else{//非头删
prev.next = cur.next;
}
size--;
return cur.value;
}else{
prev = cur;
cur = cur.next;
}
}
return -1;
}
开散列实现HashSet()
1.contains(value) :返回哈希集合中是否存在这个值。
class Node{
Node next;
int key;
public Node(int key){
this.key = key;
next = null;
}
}
class MyHashSet {
private Node[] arr = new Node[10];
private int size = 0;
private static final double LOAD_FACTORY = 0.7;
public double loadFactory(){//计算负载因子
return size * 1.0 / arr.length;
}
public boolean contains(int key) {
int index = key % arr.length;
Node cur = arr[index];
while(cur != null){
if(cur.key == key){
return true;
}
cur = cur.next;
}
return false;
}
2.add(value):向哈希集合中插入一个值。
public void add(int key) {
if(loadFactory() >= LOAD_FACTORY){//负载因子过大扩容调整
resize();
}
int index = key % arr.length;
Node cur = arr[index];
while(cur != null){
if(cur.key == key){
return;
}
cur = cur.next;
}
Node node = new Node(key);
node.next = arr[index];
arr[index] = node;
}
public void resize(){//扩容
Node[] newArray = new Node[arr.length * 2];
for(int i = 0; i < arr.length; i++){//遍历原数组,将原数组中的数据全部拷贝到新数组中
Node cur = arr[i];//头节点
while(cur != null){//遍历此下标处的链表
Node next = cur.next;
int index = cur.key % newArray.length;//新表中应该插入的位置
cur.next = newArray[index];//进行头插
newArray[index] = cur;
cur = next;
}
}
arr = newArray;
}
3.remove(value):将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
public void remove(int key) {
int index = key % arr.length;
Node cur = arr[index];
Node prev = null;
while(cur != null){
if(cur.key == key){
if(prev == null){
arr[index] = cur.next;
}else{
prev.next = cur.next;
}
return;
}else{
prev = cur;
cur = cur.next;
}
}
}