Java 自定义HashSet

前言:

HashSet是一种数据结构为基本操做(add, remove, contains and size)提供恒定的时间性能,假设哈希函数在桶之间正确地分散元素。有许多方法能够实现这种数据结构。这篇文章主要使用链表+数组在Java中简单实现hashmap。java

1.定义一个表示链表节点的类node

class Node<T> {
        T data;
        Node next;

        Node(T data) {
            this.data = data;
            this.next = null;
        }

        @Override
        public String toString() {
            return "(" + this.data + ")";
        }
    }复制代码

2.插入元素数组

插入元素,键和值将执行如下操做:数据结构

  1. 首先计算元素的哈希码,一般是一个int。两个不一样的对象能够具备相同的哈希码,由于可能存在无限数量的元素和有限数量的整数。
  2. 而后使用模数做为hashCode (key) % array_length使用哈希码计算数组中的索引。这里两个不一样的哈希码能够映射到相同的索引。
  3. 获取上面计算的此索引的连接列表将元素存储在此索引中。因为冲突,使用链表很是重要:能够使用具备相同哈希码的两个不一样键或映射到同一索引的两个不一样哈希码。

hashmap-example

插入实现计算size():app

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.删除元素ide

使用如下步骤从hashSet中删除元素:函数

  1. 计算元素中的哈希码,而后使用模运算从哈希码计算索引。
  2. 获取上面计算的索引的连接列表,并在连接列表中搜索具备此值的值。
  3. 经过更改上一个元素的下一个引用来删除节点。
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. 时间复杂ui

将不一样的Keys映射到同一索引可能会发生冲突。若是冲突的数量很是高,最坏状况的运行时间为O(N),其中N是Keys的数量。可是咱们一般假设一个良好的实现,将冲突保持在最小,查找时间为O(1)。

2.以上就是关于如何使用基于数组的链表实现hashSet.

相关文章
相关标签/搜索