ハッシュテーブルからHashMapへのデータ構造とアルゴリズム(1)

ハッシュテーブルの概要

データを格納する場合、配列とリンクリストは通常​​、データの性質に基づいてデータが格納される順序を決定しないため、データをクエリする場合、目的のデータを見つけるために前方から後方にのみ移動できます。

たとえば、int配列[12,34,56,324,24387]にキーワード324があるかどうかを照会する場合、0番目のビットからのみ照会でき、次に324を照会できます。彼の効率はo(n)です。リンクされたリストも配列に似ています。

二分探索木と赤黒木では、データの性質に従ってデータのサイズに応じてデータを保存するため、配列やリンクリストよりも検索効率がはるかに高くなります。

二分木

たとえば、図に示されているバイナリツリーの場合、配列5を探し、最初に10と比較し、次に3と比較し、最後に5を見つけます。彼のクエリ効率はo(h)です。ここで、hはバイナリツリーの高さであり、バイナリツリーの高さが予想されます。 ln(n)、赤黒ツリーはln(n)であるため、クエリ効率は配列やリンクリストよりもはるかに高くなります。

ただし、二分探索木と赤黒木は、データ間のサイズ関係のみを使用します。データの性質に従ってデータを直接配置すると、クエリの効率が最も高くなります。

最初に最も単純なケースを考えます。0〜99のデータを保存し、次に長さ100の配列を使用して保存し、次に0番目の位置に0を保存し、1を最初の位置に保存し、99を最後の位置に保存します。すると、理論的には、ビット数に相当するデータがあるかどうかに注意するだけでよいので、検索パフォーマンスはo(1)になります。

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

上記のコードは、非常に単純なハッシュテーブルを実装してデータを保存します。

ただし、通常は100個を超えるデータキーを保存し、それほど大きな配列を作成することはできません。また、通常保存されるデータの量もキーの可能な値よりもはるかに少ないため、このような大きな配列を作成するとスペースが無駄になります。

この状況を解決するために、賢人はこの問題を解決するためにハッシュ関数を考案しました。具体的には、無限キーセットがスロットkにマッピングされ、特定のデータが計算の前に保存されますハッシュ値は、ハッシュ値に対応するスロットに保存されます。

次に、問題があります。つまり、2つの可能なハッシュ値が同じである場合、どのようにそれを解決すればよいでしょうか。

最も理想的な方法は競合を回避することですが、これは不可能なので、ハッシュ値をできるだけランダムにして競合の可能性を減らし、競合が発生した場合は解決する方法があります。

競合を解決するには、リンク方式とオープンアドレス方式の2つの方法があります。競合を個別に解決するこれらの2つの方法を紹介しましょう。

オープンアドレス指定

競合を解決するためのオープンアドレス指定方法のアイデアは非常に単純です。データの一部を挿入するときに、計算されたハッシュ値に対応するスロットがすでに他のキーによって占有されている場合は、特定のルールに従って別のスロットを探し、対応するものを見つけることを知っています場所。

次に、単純なオープンアドレッシング方法を示します。つまり、スロットが占有されている場合、次の隣接スロットを見つけ、ハッシュ値も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

アドレッシングルールとハッシュ関数の選択は非常に単純で、キーがint番号の場合のみを考慮したことがわかります。しかし、実際のコーディングでは、最初に任意のオブジェクトをint値に変換し、次に対応するハッシュ値、およびオブジェクトをint値に変換する関数は、JavaのhashCode関数であり、デフォルトの実装はオブジェクトのメモリアドレスに基づいています。

リンク方式

競合を解決するリンク方法の考え方も非常に単純で、リンクリスト(または他のデータ構造)を各スロットに保存し、同じハッシュデータをリンクリストに直接格納します。

JavaのHashMapに固有であり、リンクメソッドを使用して競合を解決します。そのため、ここではこのメソッドを実装せず、次の記事でHashMapを紹介するときに、linkメソッドについて詳しく説明します。

リンク方式やオープンアドレス指定方式に関係なく、競合を解決できますが、競合は解決されますが、ハッシュの競合の量が非常に多い場合、パフォーマンスも大幅に低下することを認識しておく必要があります。挿入されるデータの量が比較的多い場合は、ハッシュ関数が再選択されます。新しいハッシュ関数にはより多くのスロットがあり、競合の可能性が低くなります。このプロセスは、再ハッシュプロセスと呼ばれます。これの特定の実装は、HashMapソースコードが解釈されるときにも導入されます。

19件の元の記事を公開しました 賞賛されました8 訪問4041

おすすめ

転載: blog.csdn.net/u014068277/article/details/103554564