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.

相關文章
相關標籤/搜索