二分探索木とハッシュ表

二分探索木の概念

二分探索木は、空の木または以下の性質を持つ二分木です。

  • 左部分木が空でない場合、左部分木のすべてのノードの値は根ノードの値より小さい。
  • 右部分木が空でない場合、右部分木のすべてのノードの値は根ノードの値より大きい。
  • 左右の部分木もまた二分探索木である。

TreeSetやTreeMapは内部的に赤黒木(平衡二分探索木)を使用しており、中間順巡回の結果がソートされたリストとなります。これらの集合クラスに格納されるデータは必ず比較可能な型である必要があります。

int[] array = {5, 3, 4, 1, 7, 8, 2, 6, 0, 9};

ノードの定義

static class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(int val) {
        this.val = val;
    }
}

検索操作

public boolean search(int key) {
    TreeNode cur = root;
    while (cur != null) {
        if (key < cur.val) {
            cur = cur.left;
        } else if (key > cur.val) {
            cur = cur.right;
        } else {
            return true;
        }
    }
    return false;
}

挿入操作

public boolean insert(int val) {
    if (root == null) {
        root = new TreeNode(val);
        return true;
    }
    TreeNode cur = root;
    TreeNode parent = null;
    while (cur != null) {
        parent = cur;
        if (val < cur.val) {
            cur = cur.left;
        } else if (val > cur.val) {
            cur = cur.right;
        } else {
            return false;
        }
    }
    TreeNode newNode = new TreeNode(val);
    if (val < parent.val) {
        parent.left = newNode;
    } else {
        parent.right = newNode;
    }
    return true;
}

削除操作

削除操作は複雑であり、次の手順を踏みます。

private void removeNode(TreeNode cur, TreeNode parent) {
    if (cur.left == null) {
        replaceChild(parent, cur, cur.right);
    } else if (cur.right == null) {
        replaceChild(parent, cur, cur.left);
    } else {
        TreeNode successorParent = cur;
        TreeNode successor = cur.right;
        while (successor.left != null) {
            successorParent = successor;
            successor = successor.left;
        }
        cur.val = successor.val;
        removeNode(successor, successorParent);
    }
}

private void replaceChild(TreeNode parent, TreeNode oldChild, TreeNode newChild) {
    if (parent == null) {
        root = newChild;
    } else if (parent.left == oldChild) {
        parent.left = newChild;
    } else {
        parent.right = newChild;
    }
}

ハッシュ表の概念

ハッシュ表は、要素のキーとその保存位置の間に直接的な対応関係を持つ構造です。理想的な検索方法として、ハッシュ関数によって要素の保存位置を決定し、比較なしに素早く要素を見つけられるようにします。

ハッシュ衝突

異なるキーが同じハッシュアドレスを持つ現象を「ハッシュ衝突」と呼びます。これを避けるために、効果的なハッシュ関数の設計が必要です。

public class HashTable {

    static class Entry {
        public int key;
        public int value;
        public Entry next;

        public Entry(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    private Entry[] buckets;
    private int size;

    public HashTable() {
        buckets = new Entry[16];
    }

    public void put(int key, int value) {
        int index = hash(key);
        Entry entry = buckets[index];
        while (entry != null) {
            if (entry.key == key) {
                entry.value = value;
                return;
            }
            entry = entry.next;
        }
        Entry newEntry = new Entry(key, value);
        newEntry.next = buckets[index];
        buckets[index] = newEntry;
        size++;
    }

    public int get(int key) {
        int index = hash(key);
        Entry entry = buckets[index];
        while (entry != null) {
            if (entry.key == key) {
                return entry.value;
            }
            entry = entry.next;
        }
        return -1;
    }

    private int hash(int key) {
        return Math.abs(key % buckets.length);
    }
}

Javaでの実装例

TreeMapの使用例

import java.util.Map;
import java.util.TreeMap;

public class Example {
    public static void testMap() {
        Map<String, String> map = new TreeMap<>();
        map.put("林冲", "豹子头");
        map.put("鲁智深", "花和尚");
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }
    }
}

HashSetの使用例

import java.util.HashSet;
import java.util.Set;

public class Example {
    public static void testSet() {
        Set<String> set = new HashSet<>();
        set.add("apple");
        set.add("banana");
        for (String s : set) {
            System.out.println(s);
        }
    }
}

性能分析

ハッシュ表の挿入、削除、検索の時間計算量は通常O(1)と見なされます。ただし、衝突が多い場合は性能が低下します。

タグ: Java HashMap TreeMap hashset BinarySearchTree

5月21日 10:14 投稿