综述:

散列是一种码位一体化搜索解决方案

它将元素关键码(key)映射到实际存储位置附近,实现了事实上的分治。搜索复杂度接近O(1)。

只要选择好合适的散列函数和冲突解决办法,散列就快速,高效,优雅。

散列函数以除数留余法最佳,冲突解决方法以开散列法最好。

 

客:散列背后的思想是什么?

主:其他搜索解决方案都是从搜索树出发,尽可能减少搜索树的深度。散列与它们截然不同!

散列是借鉴了数组的思想。数组为什么查找高效?因为它有索引,直接标志了存储位置。

那么如果我们把元素的关键码作为索引,也就能取得和数组一样的搜索速度。

这也就是我说的码位一体化。

当然实际实现要解决很多问题,比如冲突,不过散列很多情况下都是最高效的搜索数据结构。

客:散列有什么作用?

主:散列是一种码位一体化解决方案。为了解决快速查找与插入元素的问题。

客:它为什么快?

主:散列将待查找元素的标志码(key)映射到具体的存储空间附近(一般是数组),把搜索整个存储空间变为搜索冲突区域,

由于冲突区域远小于存储空间,这样就实现了事实上的分治。散列的快速也来源于此。实际上,只要冲突不多,散列的查找复杂度接近O(1)。

客:什么是散列函数?

主:hash在英文中是“弄乱”意思。散列函数就是把元素的关键码,均匀的映射到存储空间,以利于查找。

客:常见散列函数有哪些?

主:除留余数法,数字分析法,平方取中法,折叠法。其中除留余数法最好。

除留余数法也就是选一个小于等于存储空间大小的最大质数,然后将关键码对其求余(%),将余数作为存储空间。

客:什么是冲突?

主:由于映射不可能均匀,总会出现将两个不同元素映射到同一存储位置的情况出现,这就是冲突。

客:怎么解决冲突?

主:这要根据实际存储实现来定。闭散列存储实现(也就是用数组)解决冲突方法就是继续查看下一存储位置,如果空就插入,不空就继续查看。

所以当散列很满时,映射位置往往不是实际存储位置。这也是我为什么说实际存储位置在映射位置附近的原因。不过如果冲突探测方法均匀的话,冲突区域远小于整体存储空间,散列仍然高效。

因此,闭散列的冲突探测函数就是想把探测位置尽可能分散,避免某位置冲突多而对整体造成影响。

开散列方法是数组链表混合存储。数组存放每个映射位置的起始地址,链表存放冲突元素。

开散列方法是最好的冲突解决方法,因为它实现了事实上的冲突映射均匀分布。

客:闭散列的冲突探测函数有哪些?

主:有线性探测法,二次探测法,双散列法等。整体来说,解决冲突实际上进行的是二次散列映射,映射的越均匀,效能越好。

这样是开散列为什么比闭散列好的原因。开散列实现了事实上最好的冲突映射,虽然它是从存储层次解决的。

客:散列搜索性能与什么有关?

主:只与装载因子有关。装载因子就是衡量表是否满的标志。取值在0-1之间,0表示空,1表示满。

装载因子越小,搜索性能越好。闭散列搜索因子最好不要超过0.5,不过如果开散列实现的话,即使装载因子接近1,也能保证很好的搜索性能。

 

下面给出闭散列线性探测法的一个实现:

class Data implements Comparable<Data>{
    int key;
    int value;
    public Data(){}
    public Data(int key, int value){
        this.key = key;
        this.value = value;
    }
    @Override
    public int compareTo(Data data) {
        return this.key - data.key;
    }
}

/**
 * closed hashTable: stored by cycle array.
 * it is just an example.
 * Note that closed hashTable is not a good implement of hashTable, comparing to open hashTable.
 */
public class MyHashSet {
    private int setSize;
    private int elementNum;
    private String[] setInfo;
    private Data[] hashSet;
    public MyHashSet(){
        this.setSize = 6;  // the size is small, because it is easy to test :)
        this.elementNum = 0;
        this.hashSet = new Data[this.setSize];
        this.setInfo = new String[this.setSize]; //"empty", "deleted" or "active"
        for (int i = 0; i < setInfo.length; i++) {
            setInfo[i] = "empty";
        }
    }
    private int hash(int key){
        return key % 5;
    }

    /**
     * Find the storage position of element
     * This is a private element. because it cannot directly reflect the real position of element.
     * @param element
     * @return position
     */
    private int searchPos(Data element){
        int pos = this.hash(element.key);
        int j = pos;
        do {
            if (setInfo[j].equals("empty") || setInfo[j].equals("active") && hashSet[j].key == element.key){
                break;
            }
            j = (j + 1) % this.setSize;
        } while (j != pos);

        return j;
    }

    /**
     * Find the position of element.if find, return position; else, return -1;
     * @param element
     * @return
     */
    public int search(Data element){
        int pos = this.searchPos(element);
        if (setInfo[pos].equals("active") && hashSet[pos].key == element.key){
            return pos;
        }else {
            return -1;
        }
    }

    /**
     * insert a new element into hashSet
     * @param element
     * @return
     */
    public boolean insert(Data element){
        if (this.elementNum == this.setSize){
            System.err.println("The Set is FULL!");
            return false;
        }
        int pos = searchPos(element);
        if (!setInfo[pos].equals("active")){
            setInfo[pos] = "active";
            hashSet[pos] = element;
            this.elementNum++;
            return true;
        }else {
            if (hashSet[pos].key == element.key){
                System.err.println("Already have!");
                return false;
            }else {
                System.err.println("The Set is logically full!");
                return false;
            }
        }
    }

    /**
     * delete the element of hashSet
     * @param element
     * @return if delete successfully , return true; else, false. 
     */
    public boolean delete(Data element){
        if (this.elementNum == 0){
            System.err.println("The Set is Empty!");
            return false;
        }
        int pos = searchPos(element);
        if (setInfo[pos].equals("active") && hashSet[pos].key == element.key){
            setInfo[pos] = "deleted";
            this.elementNum--;
            return true;
        }else {
            System.err.println("Not have!");
            return false;
        }
    }

    
    public static void main(String[] args){
        MyHashSet hashSet = new MyHashSet();

        hashSet.insert(new Data(1,11));
        hashSet.insert(new Data(13,12));
        hashSet.insert(new Data(3,13));
        hashSet.insert(new Data(4,14));
        hashSet.insert(new Data(16,15));
        hashSet.insert(new Data(5,16));
        for (Data d : hashSet.hashSet){
            System.out.println(d.value);
        }
        System.out.println(hashSet.elementNum);
        hashSet.insert(new Data(14,4));

    }
}