Algorithms - Week 4-1 Elementary Symbol Tables

Symbol Table API

Key-value pair abstraction.

  • Insert a value with specified key.
  • Given a key, search for the corresponding value.

Conventions

  • values are not null.
  • method get() returns null if key not present.
  • Method put() overwrites old value with new value.

Intended consequences.

  • Easy to implement contains().
  • Can implement lazy version of delete().

Best practices. Use immutable types for symbol table keys.

Value type. Any generic type.

Key type: several natural assumptions.

  • Assume keys are Comparable, use compareTo().
  • Assume keys are any generic type, use equals() to test equality.
  • Assume keys are any generic type, use equals() to test equality;
    use hashCode() to scramble key.

Equality test

All Java classes inherit a method equals().

public final class Date implements Comparable<Date> // final??
{
    private final int month;
    private final int day;
    private final int year;
    ...
    public boolean equals(Object y)
    {
        if (y == this) return true;
        if (y == null) return false;
        if (y.getClass() != this.getClass()) // vs. instanceof??
            return false;
        Date that = (Date) y;
        if (this.day != that.day ) return false;
        if (this.month != that.month) return false;
        if (this.year != that.year ) return false;
        return true;
    }
}

“Standard” recipe for user-defined types.

  • Optimization for reference equality.
  • Check against null.
  • Check that two objects are of the same type and cast.
  • Compare each significant field:
    if field is a primitive type, use ==
    if field is an object, use equals()
    if field is an array, apply to each entry

Best practices.

  • No need to use calculated fields that depend on other fields.
  • Compare fields mostly likely to differ first.
  • Make compareTo() consistent with equals().
    x.equals(y) if and only if (x.compareTo(y) == 0)

    ST<String, Integer> st = new ST<String, Integer>();
    

Frequency counter.

Elementary Implementations

Sequential search in a linked list

Data structure. Maintain an (unordered) linked list of key-value pairs.

Binary search in an ordered array

Data structure. Maintain an ordered array of key-value pairs.

public Value get(Key key)
{
    if (isEmpty()) return null;
    int i = rank(key);
    if (i < N && keys[i].compareTo(key) == 0) return vals[i];
    else return null;
}
private int rank(Key key)
{
    int lo = 0, hi = N-1;
    while (lo <= hi)
    {
        int mid = lo + (hi - lo) / 2;
        int cmp = key.compareTo(keys[mid]);
        if (cmp < 0) hi = mid - 1;
        else if (cmp > 0) lo = mid + 1;
        else if (cmp == 0) return mid;
    }
    return lo;
}

Problem. To insert, need to shift all greater keys over.

Ordered Operations

如果 symbol table 是有序的,可以有很多方法可以提供。

public class ST<Key extends Comparable<Key>, Value>
    ST()                            create an ordered symbol table
    void put(Key key, Value val)    put key-value pair into the table (remove key from table if value is null)
    Value get(Key key)              value paired with key (null if key is absent)
    void delete(Key key)            remove key (and its value) from table
    boolean contains(Key key)       is there a value paired with key?
    boolean isEmpty()               is the table empty?
    int size()                      number of key-value pairs
    Key min()                       smallest key
    Key max()                       largest key
    Key floor(Key key)              largest key less than or equal to key
    Key ceiling(Key key)            smallest key greater than or equal to key
    int rank(Key key)               number of keys less than key
    Key select(int k)               key of rank k
    void deleteMin()                delete smallest key
    void deleteMax()                delete largest key
    int size(Key lo, Key hi)        number of keys in [lo..hi]
    Iterable<Key> keys(Key lo, Key hi) keys in [lo..hi], in sorted order
    Iterable<Key> keys()            all keys in the table, in sorted order

Binary Search Trees

Definition. A BST is a binary tree in symmetric order.

Symmetric order. Each node has a key, and every node’s key is:

  • Larger than all keys in its left subtree.
  • Smaller than all keys in its right subtree.

BST representation in Java

private class Node
{
    private Key key;
    private Value val;
    private Node left, right;
    public Node(Key key, Value val)
    {
        this.key = key;
        this.val = val;
    }
}

Search. If less, go left; if greater, go right; if equal, search hit.

Insert. If less, go left; if greater, go right; if null, insert.

Get. Return value corresponding to given key, or null if no such key.

public Value get(Key key)
{
    Node x = root;
    while (x != null)
    {
        int cmp = key.compareTo(x.key);
        if (cmp < 0) x = x.left;
        else if (cmp > 0) x = x.right;
        else if (cmp == 0) return x.val;
    }
    return null;
}

Cost. Number of compares is equal to 1 + depth of node.

Put. Associate value with key.

Search for key, then two cases:
・Key in tree ⇒ reset value.
・Key not in tree ⇒ add new node.

public void put(Key key, Value val)
{ 
    root = put(root, key, val); 
}
private Node put(Node x, Key key, Value val)
{
    if (x == null) return new Node(key, val);
    int cmp = key.compareTo(x.key);
    if (cmp < 0)
        x.left = put(x.left, key, val);
    else if (cmp > 0)
        x.right = put(x.right, key, val);
    else if (cmp == 0)
        x.val = val;
    return x;
}

Cost. Number of compares is equal to 1 + depth of node.

Remark. Tree shape depends on order of insertion.

Correspondence between BSTs and quicksort partitioning

Ordered Operations in BSTs

Minimum. Smallest key in table.

Maxmum. Largest key in table.

floor, ceil

public Key floor(Key key)
{
    Node x = floor(root, key);
    if (x == null) return null;
    return x.key;
}
private Node floor(Node x, Key key)
{
    if (x == null) return null;
    int cmp = key.compareTo(x.key);
    if (cmp == 0) return x;
    if (cmp < 0) return floor(x.left, key);
    Node t = floor(x.right, key);
    if (t != null) return t;
    else return x;
}

Subtree counts

In each node, we store the number of nodes in the subtree rooted at that node; to implement size(), return the count at the root.

private class Node
{
    private Key key;
    private Value val;
    private Node left;
    private Node right;
    private int count; // number of nodes in subtree
}

public int size()
{ return size(root); }

private int size(Node x)
{
    if (x == null) return 0;
    return x.count;
}

private Node put(Node x, Key key, Value val)
{
    if (x == null) return new Node(key, val, 1);
    int cmp = key.compareTo(x.key);
    if (cmp < 0) x.left = put(x.left, key, val);
    else if (cmp > 0) x.right = put(x.right, key, val);
    else if (cmp == 0) x.val = val;
    x.count = 1 + size(x.left) + size(x.right);
    return x;
}

public int rank(Key key)
{ 
    return rank(key, root); 
}
private int rank(Key key, Node x)
{
    if (x == null) return 0;
    int cmp = key.compareTo(x.key);
    if (cmp < 0) return rank(key, x.left);
    else if (cmp > 0) return 1 + size(x.left) + rank(key, x.right);
    else if (cmp == 0) return size(x.left);
}

Inorder traversal

Traverse left subtree.
Enqueue key.
Traverse right subtree.

public Iterable<Key> keys()
{
    Queue<Key> q = new Queue<Key>();
    inorder(root, q);
    return q;
}
private void inorder(Node x, Queue<Key> q)
{
    if (x == null) return;
    inorder(x.left, q);
    q.enqueue(x.key);
    inorder(x.right, q);
}

Property. Inorder traversal of a BST yields keys in ascending order.

Deletion in BSTs

To delete the minimum key:

  • Go left until finding a node with a null left link.
  • Replace that node by its right link.
  • Update subtree counts.

    public void deleteMin()
    { 
        root = deleteMin(root); 
    }
    
    private Node deleteMin(Node x)
    {
        if (x.left == null) return x.right;
        x.left = deleteMin(x.left);
        x.count = 1 + size(x.left) + size(x.right);
        return x;
    }
    

Hibbard deletion

To delete a node with key k: search for node t containing key k.

Case 0. [0 children] Delete t by setting parent link to null.

Case 1. [1 child] Delete t by replacing parent link.

Case 2. [2 children]
・Find successor x of t.
・Delete the minimum in t’s right subtree.
・Put x in t’s spot.

public void delete(Key key)
{ 
    root = delete(root, key); 
}

private Node delete(Node x, Key key) {
    if (x == null) return null;
    int cmp = key.compareTo(x.key);
    if (cmp < 0) x.left = delete(x.left, key);
    else if (cmp > 0) x.right = delete(x.right, key);
    else {
        if (x.right == null) return x.left;
        if (x.left == null) return x.right;
        Node t = x;
        x = min(t.right);
        x.right = deleteMin(t.right);
        x.left = t.left;
    }
    x.count = size(x.left) + size(x.right) + 1;
    return x;
}

Surprising consequence. Trees not random (!) ⇒ sqrt (N) per op.

Next lecture. Guarantee logarithmic performance for all operations.

你可能感兴趣的:(BST)