Java 自定义HashSet实现
前言:
HashSet是一种数据结构为基本操作(add, remove, contains and size)提供恒定的时间性能,假设哈希函数在桶之间正确地分散元素。有许多方法可以实现这种数据结构。这篇文章主要使用链表+数组在Java中简单实现hashmap。
1.定义一个表示链表节点的类
class Node<T> {
T data;
Node next;
Node(T data) {
this.data = data;
this.next = null;
}
@Override
public String toString() {
return "(" + this.data + ")";
}
}
2.插入元素
插入元素,键和值将执行以下操作:
- 首先计算元素的哈希码,通常是一个int。两个不同的对象可以具有相同的哈希码,因为可能存在无限数量的元素和有限数量的整数。
- 然后使用模数作为hashCode (key) % array_length使用哈希码计算数组中的索引。这里两个不同的哈希码可以映射到相同的索引。
- 获取上面计算的此索引的链接列表将元素存储在此索引中。由于冲突,使用链表非常重要:可以使用具有相同哈希码的两个不同键或映射到同一索引的两个不同哈希码。
插入实现计算size():
public class MyHashSet<T> {
private static final Integer INITIAL_CAPACITY = 1 << 4; // 16
private Node<T>[] buckets;
private int size;
public MyHashSet(final int capacity) {
this.buckets = new Node[capacity];
this.size = 0;
}
public MyHashSet() {
this(INITIAL_CAPACITY);
}
public void add(T t) {
int index = hashCode(t) % buckets.length;
Node bucket = buckets[index];
Node<T> newNode = new Node<>(t);
if (bucket == null) {
buckets[index] = newNode;
size++;
return;
}
while (bucket.next != null) {
if (bucket.data.equals(t)) {
return;
}
bucket = bucket.next;
}
// 仅在最后一个元素没有添加值时才添加
if (!bucket.data.equals(t)) {
bucket.next = newNode;
size++;
}
}
public int size() {
return size;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("[");
for (Node node: buckets) {
while (node != null) {
sb.append(node);
node = node.next;
}
}
sb.append("]");
return sb.toString();
}
private int hashCode(T t) {
return t == null ? 0 : t.hashCode();
}
}
3.删除元素
使用以下步骤从hashSet中删除元素:
- 计算元素中的哈希码,然后使用模运算从哈希码计算索引。
- 获取上面计算的索引的链接列表,并在链接列表中搜索具有此值的值。
- 通过更改上一个元素的下一个引用来删除节点。
public T remove(T t) {
int index = hashCode(t) % buckets.length;
Node bucket = buckets[index];
if (bucket == null) {
throw new NoSuchElementException("No Element Found"); //匹配不上
}
if (bucket.data.equals(t)) {
buckets[index] = bucket.next;
size--;
return t;
}
Node prev = bucket;
while (bucket != null) {
if (bucket.data.equals(t)) {
prev.next = bucket.next;
size--;
return t;
}
prev = bucket;
bucket = bucket.next;
}
return null;
}
4.测试
@Test
public void testMyHashSet() {
MyHashSet<Integer> set = new MyHashSet<>();
set.add(3);
set.add(4);
set.add(8);
set.add(4);
set.add(8);
set.add(1000);
assertEquals(4, set.size());
assertEquals(Integer.valueOf(8), set.remove(8));
assertEquals(3, set.size());
}
@Test
public void testMyHashSet1() {
MyHashSet<String> set = new MyHashSet<>();
set.add("USA");
set.add("Nepal");
set.add("England");
set.add("Netherland");
set.add("Canada");
set.add("Bhutan");
assertEquals(6, set.size());
// test removal of element
assertEquals("Bhutan", set.remove("Bhutan"));
assertEquals(5, set.size());
}
5.总结
1. 时间复杂
将不同的Keys映射到同一索引可能会发生冲突。如果冲突的数量非常高,最坏情况的运行时间为O(N),其中N是Keys的数量。但是我们通常假设一个良好的实现,将冲突保持在最小,查找时间为O(1)。
2.以上就是关于如何使用基于数组的链表实现hashSet.