每日一省之—基于双数组和二分查找法实现符号表(或者字典)

本文介绍了一种基于二分查找法实现的符号表数据结构,该数据结构使用两个数组分别存储键和值,并保持键的有序性以便高效地进行查找。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天想要复习的数据结构是符号表,也称为字典。可能说到这里大家还是没明白我要复习一种什么样的数据结构。此时我不得不承认本人文笔真的是不太好。所以,还是说的更加具体点吧,这种数据结构的功能对使用者来讲,与JDK中的HashMap极其相似:可以按键值对进行数据的存储,然后根据键可以查找到与其对应的值。

只不过,我这里讲的符号表在底层实现上:存储数据利用的是两个大小相等的数组。其中一个数组存储键,另一个数组存储对应键的值。然后,在插入键值对或者根据键进行查找的时候,为了提高效率,又利用了很普通的二分查找法。说到二分查找法,对这种算法还有印象的同学应该立刻猜测到,这个存储键的数组中存储的键一定是有序的。对!能想到这,想必下面的代码要看懂是一件非常容易的事情。当然啦,在生产环境中使用它代替HashMap等数据结构可就值得商榷了,为什么,当然是效率问题啦。why? 鉴于个人理论性不强,就不在这里造次了。有兴趣的大家自己多思考啦,总之,思考有百利而无一害。有想法朋友也不妨在下面留言咯。

好了,废话不多说了,直接看代码吧。代码中有注释,我就不唠叨啦。


import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;

/**
 * 一个基于二分查找法的字典类,可以根据键值对进行存储或根据键进行查询
 */
public class BinarySearchBasedDictionary<K extends Comparable<K>, V> {

    /**
     * keys数组和values数组的初始大小(也即数组长度)
     */
    private static final int INITIAL_CAPACITY = 4;
    /**
     * 存储键的数组
     */
    private K[] keys;
    /**
     * 存储值的数组
     */
    private V[] values;
    /**
     * keys数组或者values数组中存储的元素的数目
     */
    private int N = 0;

    /**
     * 初始化存储键和值得数组的大小
     * 
     * @param initialCapacity
     */
    public BinarySearchBasedDictionary(int initialCapacity) {
        keys = (K[]) new Comparable[INITIAL_CAPACITY];
        values = (V[]) new Object[INITIAL_CAPACITY];
    }

    public BinarySearchBasedDictionary() {
        this(INITIAL_CAPACITY);
    }

    /**
     * 求存储的键值对的数量
     * 
     * @return
     */
    public int size() {
        return N;
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    public boolean contains(K key) {
        if (key == null) {
            throw new NullPointerException("key不能为空!!!");
        }
        return get(key) != null;
    }

    /**
     * 根据key查找相应的值
     * 
     * @param key
     * @return
     */
    public V get(K key) {
        if (key == null) {
            throw new NullPointerException("key不能为空!!!");
        }
        if (isEmpty()) {
            return null;
        }
        int i = rank(key);
        if (i < N && keys[i].compareTo(key) == 0) {
            return values[i];
        }
        return null;

    }

    /**
     * 计算指定的键存在于数组中的哪一个位置,此处用的是二分查找法。 因为存入数组中的键是在数组中进行有序存储的。
     * 
     * @param key
     * @return
     */
    public int rank(K key) {
        if (key == null) {
            throw new NullPointerException("key不能为空!!!");
        }
        int low = 0, high = N - 1;
        while (low <= high) {
            int mid = low + (high - low) / 2;
            int comp = key.compareTo(keys[mid]);
            if (comp < 0) {
                high = mid - 1;
            } else if (comp > 0) {
                low = mid + 1;
            } else {
                return mid;
            }
        }

        return low;
    }

    public void put(K key, V value) {

        if (key == null) {
            throw new NullPointerException("key不能为空!!!");
        }

        if (value == null) {
            remove(key);
            return;
        }

        int i = rank(key);

        /*
         * 倘若对应的键已经存在,则替换旧值为新值
         */
        if (i < N && keys[i].compareTo(key) == 0) {
            values[i] = value;
            return;
        }

        /*
         * 空间不够,则进行扩容
         */
        if (N == keys.length)
            resize(2 * keys.length);

        for (int j = N; j > i; j--) {
            keys[j] = keys[j - 1];
            values[j] = values[j - 1];
        }
        keys[i] = key;
        values[i] = value;
        N++;

        assert check();
    }

    public void remove(K key) {
        if (key == null)
            throw new NullPointerException("key不能为空!!!");
        if (isEmpty())
            return;

        // 计算键所在的位置
        int i = rank(key);

        // 如果不存在该键,直接返回
        if (i == N || keys[i].compareTo(key) != 0) {
            return;
        }

        for (int j = i; j < N - 1; j++) {
            keys[j] = keys[j + 1];
            values[j] = values[j + 1];
        }

        N--;

        keys[N] = null; // 置空, 避免游离
        values[N] = null; // 置空, 避免游离

        // 降低数组容量,避免大量空间空闲
        if (N > 0 && N == keys.length / 4) {
            resize(keys.length / 2);
        }

        assert check();
    }

    private boolean check() {
        return isSorted() && rankCheck();
    }

    /**
     * 判断存储键的数组是否有序
     * 
     * @return
     */
    private boolean isSorted() {
        for (int i = 1; i < size(); i++) {
            if (keys[i].compareTo(keys[i - 1]) < 0) {
                return false;
            }
        }
        return true;
    }

    private boolean rankCheck() {
        for (int i = 0; i < size(); i++) {
            if (i != rank(select(i))) {
                return false;
            }
        }
        for (int i = 0; i < size(); i++) {
            if (keys[i].compareTo(select(rank(keys[i]))) != 0) {
                return false;
            }

        }
        return true;
    }

    public K select(int i) {
        if (i < 0 || i >= N) {
            return null;
        }
        return keys[i];
    }

    /**
     * 重新调整底层数组的大小
     * 
     * @param capacity
     */
    private void resize(int capacity) {
        assert capacity >= N;
        K[] tempk = (K[]) new Comparable[capacity];
        V[] tempv = (V[]) new Object[capacity];
        /*
         * 把已有的元素移植到临时数组中
         */
        for (int i = 0; i < N; i++) {
            tempk[i] = keys[i];
            tempv[i] = values[i];
        }
        /*
         * 用临时数组替换旧的数组,实现扩容
         */
        keys = tempk;
        values = tempv;
    }

    public K min() {
        if (isEmpty()) {
            return null;
        }
        return keys[0];
    }

    public K max() {
        if (isEmpty()) {
            return null;
        }
        return keys[N - 1];
    }

    public K floor(K key) {
        if (key == null) {
            throw new NullPointerException("key不能为空!!!");
        }
        int i = rank(key);
        if (i < N && key.compareTo(keys[i]) == 0) {
            return keys[i];
        }
        if (i == 0) {
            return null;
        } else {
            return keys[i - 1];
        }

    }

    public K ceiling(K key) {
        if (key == null) {
            throw new NullPointerException("key不能为空!!!");
        }
        int i = rank(key);
        if (i == N) {
            return null;
        } else {
            return keys[i];
        }
    }

    public void deleteMin() {
        if (isEmpty()) {
            throw new NoSuchElementException("删除失败!没有任何元素存储其中");
        }
        remove(min());
    }

    public void deleteMax() {
        if (isEmpty()) {
            throw new NoSuchElementException("删除失败!没有任何元素存储其中");
        }
        remove(max());
    }

    public int size(K low, K high) {
        if (low == null) {
            throw new NullPointerException("low不能为空");
        }
        if (high == null) {
            throw new NullPointerException("high不能为空");
        }

        if (low.compareTo(high) > 0) {
            return 0;
        }

        if (contains(high)) {
            return rank(high) - rank(low) + 1;
        } else {
            return rank(high) - rank(low);
        }
    }

    public Iterable<K> keys() {
        return keys(min(), max());
    }

    public Iterable<K> keys(K low, K high) {
        if (low == null) {
            throw new NullPointerException("low不能为空");
        }
        if (high == null) {
            throw new NullPointerException("high不能为空");
        }
        Queue<K> queue = new LinkedList<K>();
        if (low.compareTo(high) > 0) {
            return queue;
        }
        for (int i = rank(low); i < rank(high); i++) {
            queue.offer(keys[i]);
        }
        if (contains(high)) {
            queue.offer(keys[rank(high)]);

        }
        return queue;
    }

     public static void main(String[] args) { 
         BinarySearchBasedDictionary<String, String> map = new BinarySearchBasedDictionary<String, String>();
         map.put("China", "leader");
         map.put("America", "savage");
         map.put("Japan", "dog");
         System.out.println("now this size is: " + map.size());
         map.remove("China");
         map.remove("America");
         System.out.println("now this size is: " + map.size());

        }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值