數據結構之符號表及其實現(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種符號表的預覽:

二叉查找樹

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