欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据结构之哈希表HashTable实例讲解

程序员文章站 2022-06-12 09:21:35
...

哈希表 是一种以关联方式存储数据的数据结构,在哈希表中,数据以数组格式存储,其中每个数据值都有自己唯一的索引。如果我们知道所需数据的索引,那么数据的访问就会变得非常快。 所以关键是 找到索引, 而检索 数值关键字 到 索引 的方式就是 哈希(Hashing)。

因此,在这个结构中插入和搜索操作非常快,不管数据的大小。哈希表使用数组作为存储介质,并使用散列技术(就是我们的哈希算法)生成一个索引。

哈希(Hashing)

哈希是一种将一系列键值转换成数组的一系列索引的技术。我们以取模运算(基数是20)来演示哈希算法。
数据结构之哈希表HashTable实例讲解

图解: 通过hash算法将左边的key映射到右边的存储数组中的索引值。

假定有以下值键组:注意:第二个值表示key,一般我们(是key,value)才是规范的写法。

(100, 1)
(100, 2)
(100, 42)
key分别为: 1, 2, 42; 使用哈希算法(取模,基数为20)后, 各自映射的索引值应该是: 1, 2, 2。如图:

数据结构之哈希表HashTable实例讲解

哈希碰撞

哈希碰撞:就是不同key的哈希值是一样的。我们的实例:1,2,42 进行 20 取模后,就发生了哈希碰撞,其中2 和 42 哈希后的索引值是一样的。

线性推测(Linear Probing)

发生哈希碰撞后,我们可以使用简单的线性推测,依次往下寻找直到找到一个空的位置。像42,先哈希再进行线性推测后,索引值为3。因为索引2后面的索引位置3是空的坑位。

数据结构之哈希表HashTable实例讲解

数据项

class DataItem{
        int data;
        int key; //key关键字,对其使用哈希算法
}

插入数据到hash存储结构中

/**
     * 插入新值到hash表
     * <pre>
     * hash后,发现坑位没有被占,则占有该坑位;
     * hash后,发现该坑位被占了:
     *      Step1: key和新插入的key不登,则使用线性推测方式,按队列顺序往后打探下一个坑位的情况(这是一个循环表顺序)
     *      Step2: 循环打探坑位:发现新坑位则占住
     * </pre>
     * @param item
     */
    static void insert(DataItem item) {
        boolean standFlag = false;
        //获取hash索引
        int idx = hashCode(item.key);

        if( null != hashArray[idx]  ) {

            if( hashArray[idx].key != item.key ) {

                //【被占】---》 则使用线性推测方式处理,找到空位进入
                while( null != hashArray[idx] && hashArray[idx].key != item.key ) {

                    idx = hashCode( ++idx );
                    if( null == hashArray[idx] ) {
                        hashArray[idx] = item;
                        standFlag = true;
                    }
                }
            }


        }else {
            //hash索引处为empty,则坑可用,直接存储
            hashArray[idx] = item;
            standFlag = true;
        }

        if( !standFlag ) {
            System.err.println("插入失败!");
        }
    }

检索

/**
     * 发生hash冲突时,检索效率就会低了,所以hash算法很关键,唯一的最好
     * @param key
     * @return
     */
    static DataItem search(int key) {
        int idx = hashCode(key);

        while(null != hashArray[idx]) {
            //如果存在了哈希冲突(即哈希值已经被占用了)

            if(hashArray[idx].key == key) {
                //如果hash相同的索引key值一样则检索到了
                return hashArray[idx];
            }

            //hash冲突,没有检索到,则线性推测查找
            idx++;

            //环绕检索,下一个索引位置
            idx = hashCode(idx);
        }

        return null;
    }

完整的哈希检索、插入程序示例

package org.byron4j.dsaa;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

/**
 * hash 表数据结构实例详解
 * <pre>
 * 在哈希表中,数据以数组格式存储,其中每个数据值都有自己唯一的索引
 * 哈希是一种将一系列键值转换成数组的一系列索引的技术
 * </pre>
 * @author BYRON.Y.Y
 *
 */
public class HashTableExample {

    private static int SIZE = 20;

    private static DataItem[] hashArray = new DataItem[20];

    public static void main(String[] args) {
        System.out.println(Arrays.asList(hashArray));


        /*insert(new DataItem(100, 1));
        insert(new DataItem(100, 2));
        insert(new DataItem(100, 42));
        insert(new DataItem(100, 62));
        insert(new DataItem(100, 82));
        insert(new DataItem(100, 102));
        insert(new DataItem(100, 122));
        insert(new DataItem(100, 142));*/

        //打乱顺序测试hash
        insert(new DataItem(100, 1));
        insert(new DataItem(100, 2));
        insert(new DataItem(100, 102));
        insert(new DataItem(100, 122));
        insert(new DataItem(100, 42));
        insert(new DataItem(100, 82));
        insert(new DataItem(100, 62));
        insert(new DataItem(100, 142));

        insert(new DataItem(100, 162));
        insert(new DataItem(100, 182));
        insert(new DataItem(100, 202));
        insert(new DataItem(100, 222));
        insert(new DataItem(100, 242));
        insert(new DataItem(100, 282));
        insert(new DataItem(100, 262));
        insert(new DataItem(100, 302));

        insert(new DataItem(100,322));
        insert(new DataItem(100, 342));
        insert(new DataItem(100,362));
        insert(new DataItem(100, 382));

        System.out.println(Arrays.asList(hashArray));
    }



    static class DataItem{
        int data;
        int key;
        public DataItem(int data, int key) {
            super();
            this.data = data;
            this.key = key;
        }
        @Override
        public String toString() {
            return "DataItem [key=" + key + "]";
        }

    }

    /**简易Hash方法,取模*/
    static int hashCode(int key){
           return key % SIZE;
    }

    /**
     * 发生hash冲突时,检索效率就会低了,所以hash算法很关键,唯一的最好
     * @param key
     * @return
     */
    static DataItem search(int key) {
        int idx = hashCode(key);

        while(null != hashArray[idx]) {
            //如果存在了哈希冲突(即哈希值已经被占用了)

            if(hashArray[idx].key == key) {
                //如果hash相同的索引key值一样则检索到了
                return hashArray[idx];
            }

            //hash冲突,没有检索到,则线性推测查找
            idx++;

            //环绕检索,下一个索引位置
            idx = hashCode(idx);
        }

        return null;
    }


    /**
     * 插入新值到hash表
     * <pre>
     * hash后,发现坑位没有被占,则占有该坑位;
     * hash后,发现该坑位被占了:
     *      Step1: key和新插入的key不等,则使用线性推测方式,按队列顺序往后打探下一个坑位的情况(这是一个循环表顺序)
     *      Step2: 循环打探坑位:发现新坑位则占住
     * </pre>
     * @param item
     */
    static void insert(DataItem item) {
        boolean standFlag = false;
        //获取hash索引
        int idx = hashCode(item.key);

        if( null != hashArray[idx]  ) {
            if( hashArray[idx].key != item.key ) {
                //【被占】---》 则使用线性推测方式处理,找到空位进入
                while( null != hashArray[idx] && hashArray[idx].key != item.key ) {

                    idx = hashCode( ++idx );
                    if( null == hashArray[idx] ) {
                        hashArray[idx] = item;
                        standFlag = true;
                    }
                }
            }


        }else {
            //hash索引处为empty,则坑可用,直接存储
            hashArray[idx] = item;
            standFlag = true;
        }

        if( !standFlag ) {
            System.err.println("插入失败!");
        }
    }
}

运行结果如下:

[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]


[DataItem [key=382], DataItem [key=1], DataItem [key=2], DataItem [key=102], DataItem [key=122], DataItem [key=42], DataItem [key=82], DataItem [key=62], DataItem [key=142], DataItem [key=162], DataItem [key=182], DataItem [key=202], DataItem [key=222], DataItem [key=242], DataItem [key=282], DataItem [key=262], DataItem [key=302], DataItem [key=322], DataItem [key=342], DataItem [key=362]]