数据结构之符号表及其实现(Map)

符号表主要就是用来将一个和一个联系起来。

定义:符号表是一种存储键值对的数据结构,支持两种操作:插入(put),即将一组新的键值对存入表中;查找(get),即根据给定的键得到相应的值。

在实现前,我们遵循以下规则:

  1. 每个键只对应一个值;
  2. 当向表中存入的键值对和表中的已有的键冲突时,新的值会替代旧的值。
  3. 键不能为空
  4. 值不能为空

无序链表,java实现:


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

/**
 * @author yuan
 * @date 2019/2/25
 * @description 符号表,键值对(Map),无序链表实现
 */
public class SequentialSearchST<Key,Value> {

    /**
     * 链表首节点
     */
    private Node first;
    /**
     * 大小
     */
    private int n;


    private class Node {
        Key key;
        Value value;
        Node next;

        public Node(Key key, Value value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public Value get(Key key) {
        if (key == null) {
            throw new IllegalArgumentException("argument to get() is null");
        }
        for (Node x = first; x != null; x = x.next) {
            if (key.equals(x.key)) {
                return x.value;
            }
        }
        return null;
    }

    public void put(Key key, Value value) {
        if (key == null) {
            throw new IllegalArgumentException("first argument to put() is null");
        }
        if (value == null) {
            // 防止value为null,如果value为null,删除对应key
            delete(key);
            return;
        }

        for (Node x = first; x != null; x = x.next) {
            if (key.equals(x.key)) {
                // 命中,更新
                x.value = value;
            }
        }
        // 未命中,新建节点
        first = new Node(key, value, first);
        n++;
    }

    public boolean contains(Key key) {
        if (key == null) {
            throw new IllegalArgumentException("argument to contains() is null");
        }
        return get(key) != null;
    }

    public int size(){
        return n;
    }

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

    public void delete(Key key) {
        if (key == null) {
            throw new IllegalArgumentException("argument to contains() is null");
        }
        first = delete(first, key);
    }

    private Node delete(Node x, Key key) {
        if (x == null) {
            return null;
        }
        if (key.equals(x.key)) {
            --n;
            return x.next;
        }
        x.next = delete(x.next, key);
        return x;
    }

    public Iterable<Key> keys(){
        Queue<Key> queue = new LinkedList<>();
        for (Node x = first; x != null; x = x.next) {
            queue.offer(x.key);
        }
        return queue;
    }

    public static void main(String[] args) {
        SequentialSearchST<String, Integer> map = new SequentialSearchST<>();
        map.put("aaa", 1);
        map.put("bbb", 3);
        map.put("ccc", 2);

        System.out.println(map.get("aaa")); // 1
        System.out.println(map.size());  // 3
        System.out.println(map.get("bbb")); // 3
        map.delete("aaa");
        System.out.println(map.get("aaa")); // null

    }

}

在含用N对键值的基于(无序)链表的符号表中,未命中的查找和插入操作都需要N次比较。

基于链表实现的顺序查找是非常低效的。

 

下面使用数组来实现,同时保证键的有序

 

使用一对平行数组,一个存储键一个存储值。

由于需要对键进行比较,所有键都必须是Comparable的对象,才可以使用a.compareTo(b)来比较a和b两个键。

 

实现的核心是rank()操作,它返回表中小于给定键的键的数量。

然后实现get()方法就容易了:只用通过rank()判断给定的键是否存储在表中即可。

然后put()方法,如果给定的键存储在表中,则更新;否则,将更大的键向后移动一格,腾出位置并将键值对插入合适的位置。

 

实现代码:


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

/**
 * @author yuan
 * @date 2019/2/25
 * @description 基于有序数组的有序符号表
 */
public class BinarySearchST<Key extends Comparable<Key>, Value> {


    private Key[] keys;
    private Value[] vals;

    /**
     * 大小
     */
    private int n;

    /**
     * 默认大小
     */
    private static final int DEFAULT_CAPACITY = 2;

    public BinarySearchST() {
        this(DEFAULT_CAPACITY);
    }


    public BinarySearchST(int capacity) {
        keys = (Key[]) new Comparable[capacity];
        vals = (Value[]) new Object[capacity];
    }

    public int size(){
        return n;
    }

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

    public Value get(Key key) {
        if (key == null) {
            throw new IllegalArgumentException("argument to get() is null");
        }
        if (isEmpty()) {
            return null;
        }
        int i = rank(key);
        if (i < n && keys[i].compareTo(key) == 0) {
            return vals[i];
        }
        return null;
    }

    /**
     * 基于有序数组的二分查找
     * @param key
     * @return 返回小于键的数量
     */
    public int rank(Key key) {
        if (key == null) {
            throw new IllegalArgumentException("argument to rank() is null");
        }
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            int cmp = key.compareTo(keys[mid]);
            if (cmp < 0) {
                r = mid - 1;
            } else if (cmp > 0) {
                l = mid + 1;
            } else {
                return mid;
            }
        }
        return l;
    }

    public void put(Key key, Value val) {
        if (key == null) {
            throw new IllegalArgumentException("first argument to put() is null");
        }
        if (val == null) {
            delete(key);
            return;
        }
        int i = rank(key);
        if (i < n && keys[i].compareTo(key) == 0) {
            // 如果键存在,更新值
            vals[i] = val;
            return;
        }
        // 如果容量满,扩容
        if (n == keys.length) {
            resize(2 * keys.length);
        }
        // 移动数组,腾出位置给新的键值对
        for (int j = n; j > i; j--) {
            keys[j] = keys[j - 1];
            vals[j] = vals[j - 1];
        }
        keys[i] = key;
        vals[i] = val;
        ++n;
    }

    /**
     * 调整大小
     * @param capacity
     */
    private void resize(int capacity) {
        assert capacity >= n;
        Key[]   tempk = (Key[])   new Comparable[capacity];
        Value[] tempv = (Value[]) new Object[capacity];
        for (int i = 0; i < n; i++) {
            tempk[i] = keys[i];
            tempv[i] = vals[i];
        }
        keys = tempk;
        vals = tempv;
    }

    public boolean contains(Key key) {
        if (key == null) {
            throw new IllegalArgumentException("argument to contains() is null");
        }
        return get(key) != null;
    }

    public void delete(Key key) {
        if (key == null) {
            throw new IllegalArgumentException("argument to delete() is null");
        }
        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];
            vals[j] = vals[j + 1];
        }
        // 赋值为null,让系统回收空间
        keys[n] = null;
        vals[n] = null;
        --n;

        // 如果已用空间只占总容量的1/4
        if (n > 0 && n == keys.length / 4) {
            // 缩小容量空间
            resize(keys.length / 2);
        }
    }

    /**
     * 获取最小键的值
     * @return
     */
    public Key min() {
        if (isEmpty()) {
            throw new NoSuchElementException("called min() with empty symbol table");
        }
        return keys[0];
    }

    /**
     * 获取最大键的值
     * @return
     */
    public Key max() {
        if (isEmpty()) {
            throw new NoSuchElementException("called max() with empty symbol table");
        }
        return keys[n-1];
    }

    /**
     * 获取第k个键的值
     * @param k
     * @return
     */
    public Key select(int k) {
        if (k < 0 || k >= size()) {
            throw new IllegalArgumentException("called select() with invalid argument: " + k);
        }
        return keys[k];
    }


    /**
     * 获取所有的keys
     * @return
     */
    public Iterable<Key> keys(){
        Queue<Key> queue = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            queue.offer(keys[i]);
        }
        return queue;
    }


    public static void main(String[] args) {
        BinarySearchST<String, Integer> map = new BinarySearchST<>();
        map.put("A", 3);
        map.put("Z", 5);
        map.put("T", 8);
        map.put("S", 4);
        map.put("E", 1);

        for (String key : map.keys()) {
            System.out.println("key=" + key + ",value=" + map.get(key) + ",size=" + map.size());
            map.delete(key);
        }

    }
}

对于put()方法,虽然二分查找减少了比较次数,但无法减少运行所需时间:因为最坏情况下需要移动整个数组。

两者的对比:

 

可以看到两种方法都无法实现插入查找都是O(lgN)级别的。

为了实现更高效的插入和查找操作,我们需要用到二叉查找树

6种符号表的预览:

二叉查找树

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章