散列表

基本概念
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
1、若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表。
2、对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为碰撞(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数f(k)和处理碰撞的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址。
3、若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞。

这里写图片描述

常用的构造散列函数的方法有:
(1)、直接定址法
取关键字或关键字的某个线性函数值为散列地址,即:h(key) = key 或 h(key) = a * key + b其中a和b为常数。
(2)、数字分析法
(3)、平方取值法
取关键字平方后的中间几位为散列地址。
(4)、折叠法
将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为散列地址。
(5)、除留余数法
取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址,即: h(key) = key MOD p p ≤ m
(6)、随机数法
选择一个随机函数,取关键字的随机函数值为它的散列地址,即:h(key) = random(key)其中random为随机函数。

解决冲突的方法:
⑴链接法(拉链法)。将具有同一散列地址的记录存储在一条线性链表中。例,除留余数法中,设关键字为 (18,14,01,68,27,55,79),除数为13。散列地址为 (5,1,1,3,1,3,1),哈希散列表如图。
⑵开放定址法。如果h(k)已经被占用,按如下序列探查:(h(k)+p⑴)%TSize,(h(k)+p⑵)%TSize,…,(h(k)+p(i))%TSize,…其中,h(k)为哈希函数,TSize为哈希表长,p(i)为探查函数。在 h(k)+p(i-1))%TSize的基础上,若发现冲突,则使用增量 p(i) 进行新的探测,直至无冲突出现为止。其中,根据探查函数p(i)的不同,开放定址法又分为线性探查法(p(i) = i : 1,2,3,…),二次探查法(p(i)=(-1)^(i-1)*((i+1)/2)^2,探查序列依次为:1, -1,4, -4, 9 …),随机探查法(p(i): 随机数),双散列函数法(双散列函数h(key) ,hp (key)若h(key)出现冲突,则再使用hp (key)求取散列地址。探查序列为:h(k),h(k)+ hp(k),…,h(k)+ i*hp(k))。
⑶桶定址法。桶:一片足够大的存储空间。桶定址:为表中的每个地址关联一个桶。如果桶已经满了,可以使用开放定址法来处理。例如,插入A5,A2,A3,B5,A9,B2,B9,C2,采用线性探查法解决冲突。如图。
这里写图片描述

开放地址法的Java代码实现

public class OpenAddressHashtable<K, V> {
    private static final double DEFAULT_LOAD_FACTOR = 0.75;
    private static final int DEFAULT_INIT_CAP = 8;
    private static final boolean CHECK_INVARIANT = true;
    private static final Entry DELETED = new Entry();
    private double loadFactor;  
    private Entry<K, V>[] table;
    private int size;

    public OpenAddressHashtable() {
        this(DEFAULT_LOAD_FACTOR);
    }

    public OpenAddressHashtable(double loadFactor) {
        if (loadFactor <= 0 || loadFactor >= 1) {
            throw new IllegalArgumentException("load factor must be between (0, 1)");
        }
        @SuppressWarnings("unchecked")
        Entry<K, V>[] entries = (Entry<K, V>[])new Entry[DEFAULT_INIT_CAP];
        table = entries;
        this.loadFactor = loadFactor;
    }

    public int size() {
        return size;
    }

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

    public boolean put(K key, V value) {
        int freeSlot = findSlot(key);
        boolean inserted = (table[freeSlot] == null || table[freeSlot] == DELETED);
        if (inserted) {
            table[freeSlot] = new Entry<K, V>(key, value);
            size++;
            if (table.length * loadFactor < size) {
                rehash();
            }
        } else {
            table[freeSlot].value = value;
        }

        if (CHECK_INVARIANT) checkInvariant();
        return inserted;
    }

    private void rehash() {
        int newLen = table.length << 1;
        while (newLen * loadFactor < size) {
            newLen <<= 1;
        }
        Entry<K, V>[] oldTable = this.table;
        this.table = new Entry[newLen];
        this.size = 0; // clear size
        for (int i = 0; i < oldTable.length; i++) {
            if (oldTable[i] != null && oldTable[i] != DELETED) {
                put(oldTable[i].key, oldTable[i].value);
            }
        }
    }

    public boolean delete(K key) {
        int freeSlot = findSlot(key);
        boolean deleted = table[freeSlot] != null && table[freeSlot] != DELETED;
        if (deleted) {
            table[freeSlot] = DELETED;
            size--;
        }

        if (CHECK_INVARIANT) checkInvariant();
        return deleted;
    }

    public boolean contains(K key) {
        int freeSlot = findSlot(key);
        return table[freeSlot] != null && table[freeSlot] != DELETED;
    }

    public V get(K key) {
        int freeSlot = findSlot(key);
        if (table[freeSlot] == null || table[freeSlot] == DELETED) {
            return null;
        } else {
            return table[freeSlot].value;
        }
    }

    /**
     * 找到一个关键字为key的槽,如果找不到,则返回下一个插入key的槽,这个槽的值要么是null,要么是DELETED。
     */
    private int findSlot(K key) {
        int freeSlot = -1;
        for (int i = 0; i < table.length; i++) {
            int h = hash(key, i);
            if (table[h] == null || equals(table[h].key, key)) {
                return h;
            } else if (table[h] == DELETED && freeSlot == -1) {
                freeSlot = h;
            }
        }
        // it should never return -1
        return freeSlot;
    }

    /*
     * h(k,i)函数,h(k,0),h(k,1)..h(k,m-1)必须是0,1..m-1的一个排列,其中m
     * 是table的长度,即table.length。
     */
    private int hash(K k, int i) {
        // 线性探查,最简单的函数
         return (k.hashCode() + 3 * i) % table.length;

        // 二次探查,但是该如何选择c1,c2呢?这里选择c1=1,c2=3,但是它似乎并不能
        // 使h(k,0), h(k,1)..h(k,m-1)是0,1..m-1的一个排列。
        // return (k.hashCode() + i + 3 * i * i) % table.length;
    }

    private void checkInvariant() {
        int len = table.length;
        while (len > 1) {
            if (len % 2 != 0) {
                throw new IllegalStateException(String.format("table.length(%d) is not like 2^n", table.length));
            }
            len = len / 2;
        }

        if (loadFactor * table.length < size) {
            throw new IllegalStateException(String.format("loadFactor(%0.2f) * table.length(%d) < size(%d)", loadFactor, table.length, size));
        }

        int acutalSize = 0;
        for (int i = 0; i < table.length; i++) {
            if (table[i] != null && table[i] != DELETED) {
                Entry<K, V> e = table[i];
                boolean found = false;
                for (int j = 0; j < table.length; j++) {
                    int h = hash(e.key, j);
                    if (table[h] == null) {
                        break;
                    }
                    else if (table[h] != DELETED && equals(table[h].key, e.key)) {
                        found = true;
                    }
                }
                if (!found)
                    throw new IllegalStateException(String.format("Cannot find key \"%s\"", e.key));
                acutalSize++;
            }
        }
        if (size != acutalSize) {
            throw new IllegalStateException(String.format("actual size(%d) not equal size(%d)", acutalSize, size));
        }   
    }

    private static boolean equals(Object o1, Object o2) {
        return o1 == o2 || (o1 != null && o1.equals(o2));
    }

    private static class Entry<K, V> {
        K key;
        V value;
        public Entry() {}
        public Entry(K k, V v) { key = k; value = v; }
        public boolean equals(Object o) {
            if (o == null || !(o instanceof Entry)) return false;
            return OpenAddressHashtable.equals(key, ((Entry)o).key);
        }
        public int hashCode() { return key == null ? 0 : key.hashCode(); }
    }

    public static void main(String[] args) {
        OpenAddressHashtable<Integer, String> hashtable = new OpenAddressHashtable<Integer, String>();
        hashtable.put(1, "one");
        System.out.println(hashtable.get(1));

        hashtable.put(1, "oneone");
        System.out.println(hashtable.get(1));

        for (int i = 0; i < 20; i++) {
            hashtable.put(i, "value " + i);
        }
        System.out.println(hashtable.size);
        System.out.println(hashtable.get(1));

        hashtable.delete(1);
        System.out.println(hashtable.get(1));
        System.out.println(hashtable.size);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值