CuckooHash(布谷鳥散列)

概念:
  • 定義:
    CuckooHash(布谷鳥散列)是為了解決哈希沖突問題而提出配深,利用較少的計算換取較大的空間兆蕉。
  • 特點(diǎn):
    占用空間少昧穿,查詢速度快厕妖。
  • 來源:
    之所以起這個名字是因為布谷鳥生性貪婪第美,不自己筑巢蝶锋,而是在別的鳥巢里面鳥蛋孵化,先成長的幼鳥會將別的鳥蛋擠出什往,這樣獨(dú)享“母愛”扳缕,類似于哈希沖突處理過程。
  • 算法描述:

使用hashA别威、hashB計算對應(yīng)的key位置:

1躯舔、兩個位置均為空,則任選一個插入省古;
2粥庄、兩個位置中一個為空,則插入到空的那個位置
3豺妓、兩個位置均不為空惜互,則踢出一個位置后插入,被踢出的對調(diào)用該算法琳拭,再執(zhí)行該算法找其另一個位置训堆,循環(huán)直到插入成功。
4臀栈、如果被踢出的次數(shù)達(dá)到一定的閾值蔫慧,則認(rèn)為hash表已滿,并進(jìn)行重新哈希rehash

轉(zhuǎn)自:http://www.blogs8.cn/posts/WyXIBP4

實現(xiàn)過程:
  • 我們知道實現(xiàn)布谷鳥散列是需要一個散列函數(shù)的集合权薯。因此姑躲,我們要定義一個接口來獲取到這樣的一個集合睡扬。
public interface HashFamily <AnyType>{
    //根據(jù)which來選擇散列函數(shù),并返回hash值
    int hash(AnyType x, int which);
    //返回集合中散列函數(shù)的個數(shù)
    int getNumberOfFunctions();
    //獲取到新的散列函數(shù)
    void generateNewFunctions();
}
  • 定義變量:
    //定義最大裝填因子為0.4
    private static final double MAX_LOAD = 0.4;
    //定義rehash次數(shù)達(dá)到一定時黍析,進(jìn)行
    private static final int ALLOWED_REHASHES = 1;
    //定義默認(rèn)表的大小
    private static final int DEFAULT_TABLE_SIZE = 101;
    //定義散列函數(shù)集合
    private final HashFamily<? super AnyType> hashFunctions;
    //定義散列函數(shù)個數(shù)
    private final int numHashFunctions;
    //定義當(dāng)前表
    private AnyType[] array;
    //定義當(dāng)前表的大小
    private int currentSize;
    //定義rehash的次數(shù)
    private int rehashes = 0;
    //定義一個隨機(jī)數(shù)
    private Random r = new Random();
  • 初始化操作:
    public CuckooHashTable(HashFamily<? super AnyType> hf){
        this(hf, DEFAULT_TABLE_SIZE);
    }
    //初始化操作
    public CuckooHashTable(HashFamily<? super AnyType> hf, int size){
        allocateArray(nextPrime(size));
        doClear();
        hashFunctions = hf;
        numHashFunctions = hf.getNumberOfFunctions();
    }

    public void makeEmpty(){
        doClear();
    }
    //清空操作
    private void doClear(){
        currentSize = 0;
        for (int i = 0; i < array.length; i ++){
            array[i] = null;
        }
    }
    //初始化表
    private void allocateArray(int arraySize){
        array = (AnyType[]) new Object[arraySize];
    }
  • 定義hash函數(shù):
    /**
     *
     * @param x 當(dāng)前的元素
     * @param which 選取的散列函數(shù)對應(yīng)的位置
     * @return
     */
    private int myHash(AnyType x, int which){
        //調(diào)用散列函數(shù)集合中的hash方法獲取到hash值
        int hashVal = hashFunctions.hash(x, which);
        //再做一定的處理
        hashVal %= array.length;
        if (hashVal < 0){
            hashVal += array.length;
        }
        return hashVal;
    }
  • 查詢元素是否存在:
    /**
     * 查詢元素的位置卖怜,若找到元素,則返回其當(dāng)前位置阐枣,否則返回-1
     * @param x
     * @return
     */
    private int findPos(AnyType x){
        //遍歷散列函數(shù)集合马靠,因為不確定元素所用的散列函數(shù)為哪個
        for (int i = 0; i < numHashFunctions; i ++){
            //獲取到當(dāng)前hash值
            int pos = myHash(x, i);
            //判斷表中是否存在當(dāng)前元素
            if (array[pos] != null && array[pos].equals(x)){
                return pos;
            }
        }
        return -1;
    }
public boolean contains(AnyType x){
        return findPos(x) != -1;
    }
  • 刪除元素:
    /**
     * 刪除元素:先查詢表中是否存在該元素,若存在蔼两,則進(jìn)行刪除該元素
     * @param x
     * @return
     */
    public boolean remove(AnyType x){
        int pos = findPos(x);
        if (pos != -1){
            array[pos] = null;
            currentSize --;
        }
        return pos != -1;
    }
  • 插入元素:
    /**
     * 插入:先判斷該元素是否存在甩鳄,若存在,在判斷表的大小是否達(dá)到最大負(fù)載额划,
     * 若達(dá)到妙啃,則進(jìn)行擴(kuò)展,最后調(diào)用insertHelper方法進(jìn)行插入元素
     * @param x
     * @return
     */
    public boolean insert(AnyType x){
        if (contains(x)){
            return false;
        }
        if (currentSize >= array.length * MAX_LOAD){
            expand();
        }
        return insertHelper(x);
    }
  • 具體的插入過程:
 * a. 先遍歷散列函數(shù)集合俊戳,找出元素所有的可存放的位置揖赴,若找到的位置為空,則放入即可抑胎,完成插入
 * b. 若沒有找到空閑位置燥滑,隨機(jī)產(chǎn)生一個位置
 * c. 將插入的元素替換隨機(jī)產(chǎn)生的位置,并將要插入的元素更新為被替換的元素
 * d. 替換后阿逃,回到步驟a.
 * e. 若超過查找次數(shù)铭拧,還是沒有找到空閑位置,那么根據(jù)rehash的次數(shù)盆昙,判斷是否需要進(jìn)行擴(kuò)展表羽历,若超過rehash的最大次數(shù),則進(jìn)行擴(kuò)展表淡喜,否則進(jìn)行rehash操作秕磷,并更新散列函數(shù)集合
    private boolean insertHelper(AnyType x) {
        //記錄循環(huán)的最大次數(shù)
        final int COUNT_LIMIT = 100;
        while (true){
            //記錄上一個元素位置
            int lastPos = -1;
            int pos;
            //進(jìn)行查找插入
            for (int count = 0; count < COUNT_LIMIT; count ++){
                for (int i = 0; i < numHashFunctions; i ++){
                    pos = myHash(x, i);
                    //查找成功,直接返回
                    if (array[pos] == null){
                        array[pos] = x;
                        currentSize ++;
                        return true;
                    }
                }
                //查找失敗炼团,進(jìn)行替換操作澎嚣,產(chǎn)生隨機(jī)數(shù)位置,當(dāng)產(chǎn)生的位置不能與原來的位置相同
                int i = 0;
                do {
                    pos = myHash(x, r.nextInt(numHashFunctions));
                } while (pos == lastPos && i ++ < 5);
                //進(jìn)行替換操作
                AnyType temp = array[lastPos = pos];
                array[pos] = x;
                x = temp;
            }
            //超過次數(shù)瘟芝,還是插入失敗易桃,則進(jìn)行擴(kuò)表或rehash操作
            if (++ rehashes > ALLOWED_REHASHES){
                expand();
                rehashes = 0;
            } else {
                rehash();
            }
        }
    }
  • 擴(kuò)表和rehash操作:
 private void expand(){
        rehash((int) (array.length / MAX_LOAD));
    }

    private void rehash(){
        hashFunctions.generateNewFunctions();
        rehash(array.length);
    }

    private void rehash(int newLength){
        AnyType [] oldArray = array;
        allocateArray(nextPrime(newLength));
        currentSize = 0;
        for (AnyType str : oldArray){
            if (str != null){
                insert(str);
            }
        }
    }
進(jìn)行測試:
public class CuckooHashTableTest {
    //定義散列函數(shù)集合
    private static HashFamily<String> hashFamily = new HashFamily<String>() {
        //根據(jù)which選取不同的散列函數(shù)
        @Override
        public int hash(String x, int which) {
            int hashVal = 0;
            switch (which){
                case 0:{
                    for (int i = 0; i < x.length(); i ++){
                        hashVal += x.charAt(i);
                    }
                    break;
                }
                case 1:
                    for (int i = 0; i < x.length(); i ++){
                        hashVal = 37 * hashVal + x.charAt(i);
                    }
                    break;
            }
            return hashVal;
        }
        //返回散列函數(shù)集合的個數(shù)
        @Override
        public int getNumberOfFunctions() {
            return 2;
        }

        @Override
        public void generateNewFunctions() {

        }
    };

    public static void main(String[] args){
        //定義布谷鳥散列
        CuckooHashTable<String> cuckooHashTable = new CuckooHashTable<String>(hashFamily, 5);
        String[] strs = {"abc","aba","abcc","abca"};
        //插入
        for (int i = 0; i < strs.length; i ++){
            cuckooHashTable.insert(strs[i]);
        }
        //打印表
        cuckooHashTable.printArray();
    }
}
運(yùn)行結(jié)果:
當(dāng)前散列表如下:
表的大小為:13
current pos: 1 current value: abca
current pos: 3 current value: abcc
current pos: 6 current value: aba
current pos: 8 current value: abc
CuckooHashTable完整代碼:
public class CuckooHashTable<AnyType> {

    public CuckooHashTable(HashFamily<? super AnyType> hf){
        this(hf, DEFAULT_TABLE_SIZE);
    }
    //初始化操作
    public CuckooHashTable(HashFamily<? super AnyType> hf, int size){
        allocateArray(nextPrime(size));
        doClear();
        hashFunctions = hf;
        numHashFunctions = hf.getNumberOfFunctions();
    }

    public void makeEmpty(){
        doClear();
    }

    public boolean contains(AnyType x){
        return findPos(x) != -1;
    }

    /**
     *
     * @param x 當(dāng)前的元素
     * @param which 選取的散列函數(shù)對應(yīng)的位置
     * @return
     */
    private int myHash(AnyType x, int which){
        //調(diào)用散列函數(shù)集合中的hash方法獲取到hash值
        int hashVal = hashFunctions.hash(x, which);
        //再做一定的處理
        hashVal %= array.length;
        if (hashVal < 0){
            hashVal += array.length;
        }
        return hashVal;
    }

    /**
     * 查詢元素的位置,若找到元素锌俱,則返回其當(dāng)前位置晤郑,否則返回-1
     * @param x
     * @return
     */
    private int findPos(AnyType x){
        //遍歷散列函數(shù)集合,因為不確定元素所用的散列函數(shù)為哪個
        for (int i = 0; i < numHashFunctions; i ++){
            //獲取到當(dāng)前hash值
            int pos = myHash(x, i);
            //判斷表中是否存在當(dāng)前元素
            if (array[pos] != null && array[pos].equals(x)){
                return pos;
            }
        }
        return -1;
    }

    /**
     * 刪除元素:先查詢表中是否存在該元素,若存在造寝,則進(jìn)行刪除該元素
     * @param x
     * @return
     */
    public boolean remove(AnyType x){
        int pos = findPos(x);
        if (pos != -1){
            array[pos] = null;
            currentSize --;
        }
        return pos != -1;
    }

    /**
     * 插入:先判斷該元素是否存在磕洪,若存在,在判斷表的大小是否達(dá)到最大負(fù)載诫龙,
     * 若達(dá)到析显,則進(jìn)行擴(kuò)展,最后調(diào)用insertHelper方法進(jìn)行插入元素
     * @param x
     * @return
     */
    public boolean insert(AnyType x){
        if (contains(x)){
            return false;
        }
        if (currentSize >= array.length * MAX_LOAD){
            expand();
        }
        return insertHelper(x);
    }

    /**
     * 具體的插入過程:
     * a. 先遍歷散列函數(shù)集合签赃,找出元素所有的可存放的位置谷异,若找到的位置為空,則放入即可锦聊,完成插入
     * b. 若沒有找到空閑位置歹嘹,隨機(jī)產(chǎn)生一個位置
     * c. 將插入的元素替換隨機(jī)產(chǎn)生的位置,并將要插入的元素更新為被替換的元素
     * d. 替換后括丁,回到步驟a.
     * e. 若超過查找次數(shù)荞下,還是沒有找到空閑位置伶选,那么根據(jù)rehash的次數(shù)史飞,
     * 判斷是否需要進(jìn)行擴(kuò)展表,若超過rehash的最大次數(shù)仰税,則進(jìn)行擴(kuò)展表构资,
     * 否則進(jìn)行rehash操作,并更新散列函數(shù)集合
     * @param x
     * @return
     */
    private boolean insertHelper(AnyType x) {
        //記錄循環(huán)的最大次數(shù)
        final int COUNT_LIMIT = 100;
        while (true){
            //記錄上一個元素位置
            int lastPos = -1;
            int pos;
            //進(jìn)行查找插入
            for (int count = 0; count < COUNT_LIMIT; count ++){
                for (int i = 0; i < numHashFunctions; i ++){
                    pos = myHash(x, i);
                    //查找成功陨簇,直接返回
                    if (array[pos] == null){
                        array[pos] = x;
                        currentSize ++;
                        return true;
                    }
                }
                //查找失敗吐绵,進(jìn)行替換操作,產(chǎn)生隨機(jī)數(shù)位置河绽,當(dāng)產(chǎn)生的位置不能與原來的位置相同
                int i = 0;
                do {
                    pos = myHash(x, r.nextInt(numHashFunctions));
                } while (pos == lastPos && i ++ < 5);
                //進(jìn)行替換操作
                AnyType temp = array[lastPos = pos];
                array[pos] = x;
                x = temp;
            }
            //超過次數(shù)己单,還是插入失敗,則進(jìn)行擴(kuò)表或rehash操作
            if (++ rehashes > ALLOWED_REHASHES){
                expand();
                rehashes = 0;
            } else {
                rehash();
            }
        }
    }

    private void expand(){
        rehash((int) (array.length / MAX_LOAD));
    }

    private void rehash(){
        hashFunctions.generateNewFunctions();
        rehash(array.length);
    }

    private void rehash(int newLength){
        AnyType [] oldArray = array;
        allocateArray(nextPrime(newLength));
        currentSize = 0;
        for (AnyType str : oldArray){
            if (str != null){
                insert(str);
            }
        }
    }
    //清空操作
    private void doClear(){
        currentSize = 0;
        for (int i = 0; i < array.length; i ++){
            array[i] = null;
        }
    }
    //初始化表
    private void allocateArray(int arraySize){
        array = (AnyType[]) new Object[arraySize];
    }

    public void printArray(){
        System.out.println("當(dāng)前散列表如下:");
        System.out.println("表的大小為:" + array.length);
        for (int i = 0; i < array.length; i ++){
            if (array[i] != null)
                System.out.println("current pos: " + i + " current value: " + array[i]);
        }
    }

    //定義最大裝填因子為0.4
    private static final double MAX_LOAD = 0.4;
    //定義rehash次數(shù)達(dá)到一定時耙饰,進(jìn)行
    private static final int ALLOWED_REHASHES = 1;
    //定義默認(rèn)表的大小
    private static final int DEFAULT_TABLE_SIZE = 101;
    //定義散列函數(shù)集合
    private final HashFamily<? super AnyType> hashFunctions;
    //定義散列函數(shù)個數(shù)
    private final int numHashFunctions;
    //定義當(dāng)前表
    private AnyType[] array;
    //定義當(dāng)前表的大小
    private int currentSize;
    //定義rehash的次數(shù)
    private int rehashes = 0;
    //定義一個隨機(jī)數(shù)
    private Random r = new Random();

    //返回下一個素數(shù)
    private static int nextPrime(int n){
        while (!isPrime(n)){
            n ++;
        }
        return n;
    }
    //判斷是否為素數(shù)
    private static boolean isPrime(int n){
        for (int i = 2; i <= Math.sqrt(n); i ++){
            if (n % i == 0 && n != 2){
                return false;
            }
        }
        return true;
    }

}
優(yōu)化(減少哈希碰撞):

1纹笼、將一維改成多維,使用桶(bucket)的4路槽位(slot)苟跪;
2廷痘、一個key對應(yīng)多個value;
3件已、增加哈希函數(shù)笋额,從兩個增加到多個;
4篷扩、增加哈希表兄猩,類似于第一種;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市枢冤,隨后出現(xiàn)的幾起案子援岩,更是在濱河造成了極大的恐慌,老刑警劉巖掏导,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件享怀,死亡現(xiàn)場離奇詭異,居然都是意外死亡趟咆,警方通過查閱死者的電腦和手機(jī)添瓷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來值纱,“玉大人鳞贷,你說我怎么就攤上這事∨斑耄” “怎么了搀愧?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疆偿。 經(jīng)常有香客問我咱筛,道長,這世上最難降的妖魔是什么杆故? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任迅箩,我火速辦了婚禮,結(jié)果婚禮上处铛,老公的妹妹穿的比我還像新娘饲趋。我一直安慰自己,他們只是感情好撤蟆,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布奕塑。 她就那樣靜靜地躺著,像睡著了一般家肯。 火紅的嫁衣襯著肌膚如雪龄砰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天息楔,我揣著相機(jī)與錄音寝贡,去河邊找鬼。 笑死值依,一個胖子當(dāng)著我的面吹牛圃泡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愿险,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼颇蜡,長吁一口氣:“原來是場噩夢啊……” “哼价说!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起风秤,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鳖目,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缤弦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體领迈,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年碍沐,在試婚紗的時候發(fā)現(xiàn)自己被綠了狸捅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡累提,死狀恐怖尘喝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斋陪,我是刑警寧澤朽褪,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站无虚,受9級特大地震影響缔赠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骑科,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一橡淑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咆爽,春花似錦、人聲如沸置森。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凫海。三九已至呛凶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間行贪,已是汗流浹背漾稀。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留建瘫,地道東北人崭捍。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像啰脚,于是被迫代替她去往敵國和親殷蛇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗。 張土汪:刷leetcod...
    土汪閱讀 12,748評論 0 33
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法粒梦,類相關(guān)的語法亮航,內(nèi)部類的語法,繼承相關(guān)的語法匀们,異常的語法缴淋,線程的語...
    子非魚_t_閱讀 31,664評論 18 399
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile麗語閱讀 3,844評論 0 6
  • 一、 1泄朴、請用Java寫一個冒泡排序方法 【參考答案】 public static void Bubble(int...
    獨(dú)云閱讀 1,386評論 0 6
  • 昨天是周日叼旋,一個樓盤銷售員打來電話仇哆,問我是否有意向了解“觀瀾湖·觀園”,價格約1.4萬~1.5萬/平米夫植,含裝修讹剔。 ...
    浮塵過隙閱讀 252評論 0 0