0
点赞
收藏
分享

微信扫一扫

【哈希表和trie树】

数据结构与算法之哈希表

哈希表

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
在这里插入图片描述
如上图,我们的哈希表底层是通过数组加链表(其他数据结构也行)的形式实现的,在数组的每个元素中存储的是一个链表,因为数组是有限的,每个链表中有很多元素,所以我们需要先找到对应数组的下标,然后在存储到该链表中,找下标我们使用的是hash函数;这里我们就要提到hash冲突,所谓hash冲突就是因为我们的数组有限,添加元素个数多于数组长度时,我们通过取余找该元素对应的数组下标,该下标下已经存在元素,这就是为什么在数组中使用链表存储元素的原因。
解决hash冲突的方法
链地址法:
开放地址法(Open Addressing):线性探测法,遇到hash冲突+1;平方探测,遇到哈希冲突+1,+4,+9,+16。。。二次哈希法 +hash2(key);
再哈希法Rehashing 重新计算hash值;
Colasced Hashing 融合了链地址法和开放地址法。
这种存储方式我们要避免两种极端情况的出现,一种时数组特别长,但每个数组中链表元素很少(胖短),另一种则是数组特别短,但数组中链表的元素特别多(高瘦);那如何解决这个问题呢:一个解决办法是模一个素数,例如7,我们可以得到1-9的数字比较均匀。
假设共有N个元素,数组长度为M,M<N,hash冲突的次数为N-M次,就相当于每个链表中有N/M个元素,其时间复杂度为O(logN/M);

Hashtable底层实现

我们的代码是基于数组+二分搜索树实现的。

Hashtable数组初始容量设置

package tree;

import ifce.List;
import ifce.Map;
import ifce.Set;
import lianbiao.LinkedList;

public class HashTable<K extends Comparable<K>,V> implements Map<K,V> {
    private static final int[] capacity = {
            53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
            49157, 98317, 196613, 393241, 768433, 1572869, 3145739, 6291469,
            12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741
    };

Hashtable属性定义及其初始化

//哈希表中每个桶的上限和下限,超过上限需要扩容,小于下限需要缩容,避免出现极端高瘦和矮胖情况。
    //主要原因是每个桶中最多有N/M个元素。
    private static final int upperTol = 10;
    private static final int lowerTol = 2;
    //素数表中的角标,用来标记当前哈希表的长度;
    private static int capacityIndex = 0;
    private static int M;//桶的个数
    private int size;//哈希表中的元素个数。
    //定义桶;
    private AVLTreeMap<K,V>[] hashTable;
    public HashTable(){
        M = capacity[capacityIndex];
        hashTable = new AVLTreeMap[M];
        for(int i=0;i<M;i++){
            hashTable[i] = new AVLTreeMap<>();
        }
    }

hash方法计算元素的下标

让该key的hashcode值&0x7fffffff 再对数组容量取余得到该键值对对应的下标

public int hash(K key){
        //获取key的哈希值,然后纠正负数,再对M取余,得到要去桶的角标;
        return key.hashCode() & 0x7fffffff % M;
    }

添加元素实现

通过hash算法算到下标,然后再判断该key是否存在,存在则是修改,不存在则是插入该键值对,元素个数要加1,并且要判断是否需要扩容。条件就是当元素个数超过了每个数组中平衡树元素总和,并且capacityIndex不能超出我们所给的素数组。

@Override
    public void put(K key, V value) {
        int index = hash(key);
        AVLTreeMap<K,V> map = hashTable[index];
        //存在则是修改;
        if(map.contains(key)){
            map.put(key,value);
        }else{
            map.put(key,value);
            size++;
            //判断是否扩容;
            if(size > upperTol * M && capacityIndex+1<capacity.length){
                capacityIndex++;
                resize(capacity[capacityIndex]);
            }
        }
    }

删除元素实现

删除元素也是先找到下标,然后判断该键值对是否已经存在,存在就删除,元素数减一,之后判断是否需要缩容。最终将删除的元素返回。条件就是当元素个数超过了每个数组中平衡树元素总和,并且capacityIndex不能超出我们所给的素数组。

 @Override
    public V remove(K key) {
        int index = hash(key);
        AVLTreeMap<K,V> map = hashTable[index];
        V ret = null;
        if(map.contains(key)){
            ret = remove(key);
            size--;
            //考虑缩容;
            if(size<lowerTol * M && capacityIndex - 1 >= 0){
                resize(capacity[capacityIndex]);
            }
        }
        return ret;
    }

扩容、缩容实现

扩容其实就是再新建一个容量增加的平衡树,然后将原来平衡树中的元素赋值到新的树当中。将新树重新赋给原树即可。

private void resize(int newM) {
        AVLTreeMap<K,V>[] newHashTable = new AVLTreeMap[newM];
        for(int i=0;i<newHashTable.length;i++){
            newHashTable[i] = new AVLTreeMap<>();
        }
        M =newM;
        for(int i=0;i<hashTable.length;i++){
            AVLTreeMap<K,V> map = hashTable[i];
            for(K key : map.keySet()){
                newHashTable[hash(key)].put(key,map.get(key));
            }
        }
        hashTable = newHashTable;
    }

判断是否包含某个元素

@Override
    public boolean contains(K key) {
        int index = hash(key);
        AVLTreeMap<K,V> map = hashTable[index];
        return map.contains(key);
    }

通过key获取对应的值

    @Override
    public V get(K key) {
        int index = hash(key);
        AVLTreeMap<K,V> map = hashTable[index];
        return map.get(key);
    }

通过键值对修改值

    @Override
    public void set(K key, V value) {
        int index = hash(key);
        AVLTreeMap<K,V> map = hashTable[index];
        map.set(key,value);
    }

返回元素个数及其判空操作

    @Override
    public int size() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

所有key元素遍历

@Override
    public Set<K> keySet() {
        TreeSet<K> set = new TreeSet<>();
        for(int i=0;i<hashTable.length;i++){
            AVLTreeMap<K,V> map = hashTable[i];
            for(K key : map.keySet()){
                set.add(key);
            }
        }
        return set;
    }

所有值的遍历

    @Override
    public List<V> values() {
        LinkedList<V> list = new LinkedList<>();
        for(int i=0;i<hashTable.length;i++){
            AVLTreeMap<K,V> map = hashTable[i];
            for(V value : map.values()){
                list.add(value);
            }
        }
        return list;
    }

entry键值对的遍历

    @Override
    public Set<Entry<K, V>> entrySet() {
        TreeSet<Entry<K,V>> set = new TreeSet<>();
        for(int i=0;i<hashTable.length;i++){
            AVLTreeMap<K,V> map = hashTable[i];
            for(Entry entry:map.entrySet()){
                set.add(entry);
            }
        }
        return set;
    }
举报

相关推荐

0 条评论