Estructura de datos y algoritmo de tabla hash a HashMap (1)

Introducción a la tabla hash

Cuando se almacenan datos, las matrices y las listas vinculadas generalmente no determinan el orden en que se almacenan los datos en función de la naturaleza de los datos, por lo que al consultar datos, solo pueden atravesar de adelante hacia atrás para encontrar los datos deseados;

Por ejemplo, cuando se pregunta si hay una palabra clave 324 en una matriz int [12,34,56,324,24387], solo podemos consultar desde el bit 0, y luego consultar 324, su eficiencia es o (n); la lista vinculada también Similar a las matrices.

Para los árboles de búsqueda binarios y los árboles rojo-negros, guardan los datos de acuerdo con el tamaño de los datos, utilizando la naturaleza de los datos, por lo que su eficiencia de búsqueda es mucho mayor que las matrices y las listas vinculadas;

Árbol binario

Por ejemplo, para el árbol binario como se muestra en la figura, cuando busque la matriz 5, primero compare con 10, luego compare con 3 y finalmente encuentre 5, su eficiencia de consulta es o (h), donde h es la altura del árbol binario y se espera la altura del árbol binario En (n), el árbol rojo-negro es ln (n), por lo que su eficiencia de consulta es mucho mayor que las matrices y las listas vinculadas.

Sin embargo, el árbol de búsqueda binario y el árbol rojo-negro solo usan la relación de tamaño entre los datos. Si colocamos los datos directamente de acuerdo con la naturaleza de los datos, la eficiencia de la consulta es la más alta.

Considere el caso más simple primero, queremos guardar 0-99 datos, luego usamos una matriz de longitud 100 para guardar, luego guardar 0 en la posición 0, 1 guardar en la primera posición, 99 guardar en la última posición Entonces, en teoría, nuestro rendimiento de búsqueda es o (1), porque solo debemos prestar atención a si hay datos correspondientes a la cantidad de bits.

public interface Entry<K,V> {
    K getKey();
    V getValue();
}
public class SimpleEntry implements Entry<Integer,String> {
    private Integer key;
    private String value;

    public SimpleEntry() {
    }

    public SimpleEntry(Integer key, String value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public Integer getKey() {
        return key;
    }

    @Override
    public String getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "SimpleEntry{" +
                "key=" + key +
                ", value='" + value + '\'' +
                '}';
    }
}
public class SimpleMap {
    private Entry<Integer, String>[] data;

    public SimpleMap() {
        this.data = new SimpleEntry[100];
    }

    public void put(Integer key, String value) {
        data[key] = new SimpleEntry(key, value);
    }

    public String get(Integer key) {
        Entry<Integer, String> entry = data[key];
        if (entry != null) {
            return entry.getValue();
        }
        return null;
    }

}
public class Test {
    public static void main(String[] args) {
        SimpleMap simpleMap = new SimpleMap();
        simpleMap.put(10,"adj");
        simpleMap.put(13,"dfh45");
        simpleMap.put(23,"453vete");
        simpleMap.put(67,"459vrj");
        simpleMap.put(49,"547843vnrmd");

        System.out.println(simpleMap.get(13));
        System.out.println(simpleMap.get(67));
        System.out.println(simpleMap.get(1));

    }
}

//输出
dfh45
459vrj
null

El código anterior implementa una tabla hash extremadamente simple para guardar datos.

Pero generalmente guardamos más de 100 claves de datos, no podemos crear una matriz tan grande, y la cantidad de datos generalmente guardada también es mucho menor que el valor posible de la clave, creando una matriz tan grande que desperdicia espacio.

Para resolver esta situación, a los sabios se les ocurrió una función hash para resolver este problema. Específicamente, el conjunto de claves infinitas se asigna a una ranura k, y luego los datos específicos se guardan antes del cálculo El valor hash se guarda en la ranura correspondiente al valor hash.

Entonces hay un problema, es decir, si dos posibles valores hash son iguales, ¿cómo deberíamos resolverlo?

La forma más ideal es evitar conflictos, pero esto no es posible, así que haga que el valor hash sea lo más aleatorio posible para reducir la probabilidad de conflictos, y luego, cuando hay un conflicto, hay formas de resolverlo.

Hay dos formas de resolver conflictos: el método de enlace y el método de direccionamiento abierto. Presentemos estos dos métodos para resolver conflictos por separado.

Direccionamiento abierto

La idea del método de direccionamiento abierto para resolver conflictos es muy simple: al insertar un dato, si el espacio correspondiente al valor hash calculado ya está ocupado por otras teclas, busque otro espacio de acuerdo con ciertas reglas y sepa cómo encontrar el correspondiente Ubicación.

Aquí hay un método simple de direccionamiento abierto, es decir, si la ranura está ocupada, para encontrar la siguiente ranura adyacente, y el valor hash también se selecciona directamente por el resto después de dividir por 100:

public class SimpleMap2 {
    private Entry<Integer, String>[] data;

    public SimpleMap2() {
        this.data = new SimpleEntry[100];
    }

    public void put(Integer key, String value) {
        int index = hash(key);
        Entry<Integer, String> entry = null;
        while ((entry = data[index]) != null
                && !entry.getKey().equals(key)) {
            index = getNextIndex(index);
        }
        //这个时候要么data[index]为空,要么data[index]保存的值key等于传入的key
        data[index] = new SimpleEntry(key, value);
    }

    public String get(Integer key) {
        int index = hash(key);
        Entry<Integer, String> entry = null;
        while ((entry = data[index]) != null) {
            if (entry.getKey().equals(key)){
                return entry.getValue();
            }
            index = getNextIndex(index);
        }
        return null;
    }

    private int hash(Integer key) {
        return key % 100;
    }

    private int getNextIndex(int index) {
        return index + 1;
    }

}
SimpleMap2 simpleMap2 = new SimpleMap2();
simpleMap2.put(10,"adj");
simpleMap2.put(110,"dfh45");
simpleMap2.put(11,"453vete");
simpleMap2.put(4,"459vrj");
simpleMap2.put(347,"547843vnrmd");

System.out.println(simpleMap2.get(110));
System.out.println(simpleMap2.get(4));
System.out.println(simpleMap2.get(1));

//输出
dfh45
459vrj
null

Se puede ver que las reglas de direccionamiento y la selección de la función hash son extremadamente simples, y solo consideramos el caso donde la clave es un número int. Sin embargo, en la codificación real, podemos convertir cualquier objeto a un valor int primero, y luego calcular el correspondiente El valor hash, y la función que convierte el objeto en un valor int, es la función hashCode en Java, y su implementación predeterminada se basa en la dirección de memoria del objeto.

Método de enlace

La idea del método de enlace para resolver conflictos también es muy simple: es guardar una lista vinculada (u otra estructura de datos) en cada ranura y almacenar directamente los mismos datos hash en una lista vinculada.

Específico de HashMap de Java, utiliza el método de enlace para resolver conflictos. Por lo tanto, no implementamos este método aquí. Cuando presentemos HashMap en el próximo artículo, nos daremos cuenta del método de enlace en detalle.

Independientemente del método de enlace y del método de direccionamiento abierto, pueden resolver conflictos, pero debemos darnos cuenta de que, aunque resuelven los conflictos, su rendimiento también disminuirá significativamente cuando la cantidad de conflictos de hash sea muy grande, por lo general Cuando la cantidad de datos insertados es relativamente grande, la función hash se volverá a seleccionar. La nueva función hash tiene más ranuras, lo que reduce la probabilidad de conflicto. Este proceso se denomina proceso de repetición. La implementación específica de esto también se introduce cuando se interpreta el código fuente de HashMap.

Publicado 19 artículos originales · elogiado 8 · visitas 4041

Supongo que te gusta

Origin blog.csdn.net/u014068277/article/details/103554564
Recomendado
Clasificación