哈希表的使用及实现

HashTable 哈希表

哈希表引子

“a-z”的随机字符 - 统计每个字符出现的次数

数组长度为 26
‘a’ => 0
‘z’ => 25
———————————————————————————————————————
String s = “不做限定”; 长度未知
int[] count = new int[1000] //这里1000是随便设定的

for(char ch : s.toCharArray()) {
int index = 根据字符得到下标(ch);
count[index]++;
)


引子:

public static void main(String[] args) {
        String s = "azxdbcdewlafkjdofjadsfjlzjoidfjoz";
        int [] count = new int [26];
        for (char ch : s.toCharArray()) {
            int index = ch - 'a';
            count[index]++;
        }

        System.out.println(Arrays.toString(count));
    }

哈希表的插入、删除、和查找方法

相比较于“搜索树”,哈希表的效率更高(平均 O(1));实现简单

// 元素类型,使用Integer
// 使用”拉链法“解决哈希冲突
//哈希表的时间复杂度: 插入/删除/查找 平均O(1)。——> 泊松分布

public class Node {
    Integer key;
    Node next;

    public Node(Integer key) {
        this.key = key;
    }
}
public class HashTable {
    //1、数组
    private Node[] array = new Node[11];
    //2、维护哈希表中有的元素个数
    private int size;
    //插入/删除/查找方法的定义...
}

插入:O(1)
1、根据元素,得到合法的下标(根据hash函数)
2、把元素放到 index 位置

// true: key之前不在哈希表中
// false: key之前已经在哈希表中
public boolean insert(Integer key) {
    // 1.把对象转成”int类型“
    // "hashCode()"方法的调用是核心
    int hashValue = key.hashCode();
    // 2.把 hashValue 转成合法的下标
    int index = hashValue % array.length;
    // 3.遍历 index 位置处的链表,确定 key 在不在链表中
    Node current = array[index];
    while (null != current) {
        // "equals()"方法的调用是核心
        if (key.equals(current.key)) {
            return false;
        }
        current = current.next;
    }
    // 4.把key装入节点中,并插入对应的链表中
    // 头插尾插都可以,头插相对简单
    Node node = new Node(key);
    node.next = array[index];
    array[index] = node;

    //5.维护 元素个数
    size++;

    // 6.维护负载因子,进而维护较低的冲突率
    if ((double)size / array.length >= 0.75) {
        //扩容
        扩容();
    }
    return true;
}

//总体上:时间复杂度应算成O(n) ——> 有的部分因为直接为null。
private void 扩容() {
    Node[] newArray = new Node[array.length*2];

    //搬原来的元素过来
    //不能直接按链表搬运,因为元素保存的下标和数组长度有关
    //数组长度变了,下标也会变
    //所以,需要把每个元素重新计算一次

    //遍历整个数组
    for (int i = 0;i < array.length;i++) {
        //遍历每条链表
        Node current = array[i];
        while (null != current) {
            //高效的办法是搬节点,写起来比较麻烦;
            //我们采用复制节点,简单一点
            Integer key = current.key;
            int hashValue = key.hashCode();
            int index = hashValue% newArray.length;

            // 头插尾插都可以,头插简单
            Node node = new Node(key);
            node.next = newArray[index];
            newArray[index] = node;

            current = current.next;
        }
    }

    array = newArray;
}

删除:O(1)
1、根据元素,得到合法的下标(根据hash函数)
2、把元素从数组中删除

 public boolean remove(Integer key) {
    //获取hashCode()
    int hashValue = key.hashCode();
    int index = hashValue % array.length;
    //获取元素所在位置
    Node current = array[index];
    //定义current节点的前驱节点
    Node pre = null;

    while (current != null) {
        //进行比较,查找key
        if (key.equals(current.key)) {
            //进行删除
            if (null == pre) {
                array[index] = current.next;
            }  else {
                pre.next = current.next;
            }
            //维护 元素个数
            size--;
            return true;
        }
        pre = current;
        current = current.next;
    }

    return false;
}

查找:O(1)
1、根据元素,得到合法的下标(根据hash函数)
2、判断元素在不在

**哈希冲突:**存在 k1 != k2 , 但是 hash(k1) == hash(k2) 。

 public boolean contains(Integer key) {
        int hashValue = key.hashCode();
        int index = hashValue% array.length;
        Node current = array[index];
        while (current != null) {
            if (key.equals(current.key)) {
                return true;
            }
            current = current.next;
        }
        return false;
    }

如果有人有意识地创造了“哈希冲突”。当链表长度超过8时,碰到不一般人了,
所以不再用链表存储冲突元素,而是用更高效的的搜索数据结构(Java中选用了红黑树(搜索树))

———————————————————————————————————————

关于哈希冲突

问:在保存元素不是固定范围时,”哈希冲突“有可能避免吗?
不能避免(因为存储的元素范围 远远大于 数组长度)
问:怎么解决哈希冲突?
1、线性探测
2、拉链法(Java 的 HashMap 选用这种实现)

拉链法:使用另一种数据结构来解决冲突的元素(链表)
———————————————————————————————————————
如果不是 int 类型怎么办? Person对象
所有的 Object类,都有一个方法int hashCode(); 把一个对象变成"int类型"(不一定是合法下标)
把”int 类型“转成一个合法下标:int index = hashValue % array.length

Java中哈希表的应用

哈希表的Java实现
1、纯key模型:HashSet
2、key-value模型:HashMap

public class Main {
    //"HashMap"
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        System.out.println(map.put("qwer", 123));//返回原来的值,因为原来不存在,所以返回“null”。
        System.out.println(map.put("qwer",456));//返回原来的值“123”。
        System.out.println();

        map.put("wt",666);
        map.put("gt",777);

        for (String key : map.keySet()) {
            System.out.print(key + " ");
        }
    }

    //"HashSet"
    public static void main1(String[] args) {
        Set<Integer> set = new HashSet<>();
        set.add(1);
        set.add(2);
        set.add(3);
        set.add(4);
        System.out.println(set);  //不保证顺序
        System.out.println(set.add(1));//Set中已经有“1”,所以不允许添加,返回false
        System.out.println(set);

        System.out.println(set.remove(3));//返回true
        System.out.println(set);

        System.out.println(set.contains(1));//返回true
        System.out.println(set.contains(5));//返回false
    }
}

自定义类使用HashSet和HashMap的注意要点

自定义类使用 HashSet 或者 HashMap 的key 时,需要注意:
1.必须重写hashCode() 和 equals()方法
2.如果你认为两个对象相等,则“hashCode()”值相等,并且“equals”返回true
p1.equals(p2)返回true,则最好"p1.hashCode() == p2.hashCode()"
3.p1.hashCode() == p2.hashCode(),需要保证“p1.equals(p2)”返回“true”么?
不保证,因为可能会出现“哈希冲突”。

public class Person {
	String name;
	int age;
	
	public Person(String name, int age) {
	    this.name = name;
	    this.age = age;
	}
	//这里实现equals()方法的重写 ...
}

———————————————————————————————————————
继续讨论冲突:
1、冲突无法避免 2、冲突不好 3、目标:尽可能的减少冲突

如何尽可能的减少冲突:
    1、数组大小用素数(Java中并没有怎么用)
        index = hashValue % array.length;
    2、hash 函数(hashCode())尽可能的均匀
        @override
        public int hashCode() {
            return 0;
        }
        上面的代码是一个错误示范:因为直接返回0,会使所有的元素都“哈希冲突”了。
    3、重要
        每次插入元素时的冲突率 ~ 元素个数/数组的长度(即负载因子)
        通过降低负载因子进而降低冲突率(”负载因子“ 与 “冲突率”成正比。)
            1、我们对冲突率有个上限的阈值
                所以我们对负载因子有个上限阈值
            2、要降低冲突率,需要降低负载因子
                负载因子中,元素个数不能动;所以,只能增加数组的长度(即“扩容”)。
            3、Java中一般负载因子是“0.75”,达到这个值就会扩容(一般扩充为原来数组的两倍)。
	public static void main(String[] args) {
        Person p1 = new Person("aaa",20);
        Person p2 = new Person("aaa",20);

        HashMap<Person,String> map = new HashMap<>();
        map.put(p1,"aaa");
        System.out.println(map.get(p2));
    }

    //"HashSet"
    public static void main1(String[] args) {
        Person p1 = new Person("aaa",20);
        Person p2 = new Person("aaa",20);

        HashSet<Person> set = new HashSet<>();
        set.add(p1);
        System.out.println(set.contains(p1));//true

        System.out.println(set.contains(p2));//false ——> true
        //这里如何使“p2”包含于“set”(即让上面返回true)呢?
        //必须重写equals()方法 和 hashCode()方法
    }

重写equals()方法 和 hashCode()方法

	@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);//在保证 name 和 age 分别相等的情况下,让hashCode值相等。
    }

手撕HashMap


```java
public class Node {
    Integer key;
    Integer value;
    Node next;

    public Node(Integer key, Integer value) {
        this.key = key;
        this.value = value;
    }
}
public class MyHashMap {
    private Node[] array = new Node[11];
    private int size;

    public Integer get(Integer key) {
        int hashValue = key.hashCode();
        int index = hashValue % array.length;

        Node current = array[index];
        while (null != current) {
            if(key.equals(current.key)) {
                return current.value;
            }
            current = current.next;
        }

        return null;
    }

    public Integer put(Integer key,Integer value) {
        int hashValue = key.hashCode();
        int index = hashValue% array.length;

        Node current = array[index];
        while (null != current) {
            if (key.equals(current.key)) {
                Integer oldValue = current.value;
                current.value = value;
                return oldValue;
            }
            current = current.next;
        }

        Node node = new Node(key,value);
        node.next = array[index];
        array[index] = node;

        size++;

        //判断负载因子,看是否需要扩容

        return null;
    }
}
**完.......**

你可能感兴趣的:(Java数据结构,java,数据结构)