1. 程式人生 > >查詢演算法之——符號表(引入篇)

查詢演算法之——符號表(引入篇)

符號表的主要目的是用來儲存鍵值對,也就是將一個鍵和一個值關聯起來,它的主要操作為插入和查詢。

這篇只是為下一篇文章作為拋磚引玉,為不熟悉符號表的朋友做了一個大體的介紹,在文章的結尾列出了符號表的基本操作,有一定了解的朋友可以跳的下一篇文章(二叉查詢樹)。

首先我們必須討論幾個基本問題,這在之後的思想中將會一直用到:

1、重複的鍵

符號表不允許出現重複的鍵,當向表中插入的鍵值對的鍵已經出現在標中,當前加入的鍵值對會覆蓋原有的鍵值對,也就是進行了更新。

2、空鍵或空值

鍵不能為空,這在java機制中會產生異常。我們還規定,值也不能為空。(get()方法能通過查詢鍵返回其關聯的值,當鍵不存在時get()方法會返回null)

這個規定有兩個作用:

第一、我們能判斷一個鍵值對是否存在符號表中。

第二、我們可以將null作為put()的第二個引數來實現刪除操作,

3、刪除操作

刪除操作的實現有兩種思路:即時刪除和延時刪除,延時刪除就是上面提到的,先將其值置位為空,然後在某個時刻刪除所有值為空的鍵。即時刪除也就是立即從表中刪除指定的鍵。(本文中將使用即時刪除)

4、鍵的等價性

鍵可以是任意型別(我們將其設定為泛型),可以是integer,double和string等等,java已經為他們實現了equals()方法來判斷相等,如果是自定義的鍵,則需要重寫equals()方法。

 

下面我們進入正題:

一、無序連結串列中的順序查詢

顧名思義,無序連結串列就是通常意義上的連結串列,只是連結串列的節點被定義為了鍵值對的形式,無序連結串列每次插入操作會在頭部插入一個新節點,而查詢操作是從連結串列的頭部開始一個個地遍歷符號表並使用equals()方法來進行匹配。這種方法的效率是非常低的(它的查詢操作是線性級別的),查詢第一個鍵需要1次比較,第二個鍵需要2次比較...因此平均比較次數為(1+2+...+n)/n =(n+1)/2~n/2。它無法適用於大型符號表。

 

 1 public class SequentialSearchST<Key, Value> {// 基於無序連結串列
2 private int n;// number of key-value pairs 3 private Node first; 4 5 private class Node {// 連結串列節點的定義 6 Key key; 7 Value val; 8 Node next; 9 10 public Node(Key key, Value val, Node next) { 11 this.key = key; 12 this.val = val; 13 this.next = next; 14 } 15 } 16 17 public Value get(Key key) {// 查詢給定的鍵,返回關聯的值 18 for (Node x = first; x != null; x = x.next) { 19 if (key.equals(x.key)) { 20 return x.val; 21 } 22 } 23 return null; 24 } 25 26 public void put(Key key, Value val) {// 查詢給定的鍵,找到就更新其值,沒找到將其插入最前 27 // ********************** 28 if (key == null) { 29 throw new IllegalArgumentException("first argument to put() is null"); 30 } 31 if (val == null) { 32 delete(key); 33 return; 34 } 35 // *********************防禦性程式碼,這保證了任何鍵的值都不為空 36 for (Node x = first; x != null; x = x.next) { 37 if (key.equals(x.key)) { 38 x.val = val; 39 return; 40 } 41 } 42 first = new Node(key, val, first);// 插入最前 43 n++; 44 } 45 }

 

二、有序陣列中的二分查詢

如果能用上二分查詢的思想,我們就能把查詢的效率提升到對數級別(當然前提是陣列有序)

這時插入和查詢演算法都發生了改變,為了讓陣列前提有序,插入時我們會用rank()方法來確定鍵的位置,再將此位置後的鍵後移一位,最後插入鍵。rank()返回的是鍵在符號表中排名。在這裡如果鍵存在rank()將返回鍵在有序陣列中的下標。

在這裡我們用兩個陣列分別儲存鍵值對的鍵和值,同一鍵值對在陣列中的下標是一樣的。

 

  1 public class BinarySearchST<Key extends Comparable<Key>, Value> {// 有序查詢表(基於有序陣列)
  2     private Key[] keys;
  3     private Value[] vals;
  4     private int n = 0;// 用於記錄符號表中鍵值對的個數
  5 
  6     public BinarySearchST(int capacity) {// 動態調整大小
  7         keys = (Key[]) new Comparable[capacity];
  8         vals = (Value[]) new Object[capacity];
  9     }
 10 
 11     public boolean contains(Key key) {
 12         if (key == null) {
 13             throw new IllegalArgumentException("argument to contains is null");
 14         }
 15         return get(key) != null;
 16     }
 17 
 18     public int size() {
 19         return n;
 20     }
 21 
 22     public int size(Key lo, Key hi) {
 23         if (lo == null) {
 24             throw new IllegalArgumentException("first argument to size() is null");
 25         }
 26         if (hi == null) {
 27             throw new IllegalArgumentException("second argument to size() is null");
 28         }
 29         if (lo.compareTo(hi) > 0) {
 30             return 0;
 31         }
 32         if (contains(hi)) {
 33             return rank(hi) - rank(lo) + 1;
 34         } else {
 35             return rank(hi) - rank(lo);
 36         }
 37     }
 38 
 39     public Key min() {
 40         if (isEmpty()) {
 41             throw new NoSuchElementException("called min with empty symbol table");
 42         }
 43         return keys[0];
 44     }
 45 
 46     public Key max() {
 47         if (isEmpty()) {
 48             throw new NoSuchElementException("called max with empty symbol table");
 49         }
 50         return keys[n - 1];
 51     }
 52 
 53     public Value get(Key key) {
 54         if (key == null) {
 55             throw new IllegalArgumentException("argument to get is null");
 56         }
 57         if (isEmpty()) {
 58             return null;
 59         }
 60         int i = rank(key);
 61         if (i < n && keys[i].compareTo(key) == 0) {
 62             return vals[i];
 63         }
 64         return null;
 65     }
 66 
 67     private int rank(Key key) {// 基於有序陣列的二分查詢(迭代)
 68         if (key == null) {
 69             throw new IllegalArgumentException("argument to rank is null");
 70         }
 71         int lo = 0, hi = n - 1;
 72         while (lo <= hi) {
 73             int mid = lo + (hi - lo) / 2;
 74             int cmp = key.compareTo(keys[mid]);
 75             if (cmp > 0) {
 76                 lo = mid + 1;
 77             } else if (cmp < 0) {
 78                 hi = mid - 1;
 79             } else {
 80                 return mid;
 81             }
 82         }
 83         return lo;// 找不到的情況
 84     }
 85 
 86     public Iterable<Key> keys() {
 87         return keys(min(), max());
 88     }
 89 
 90     private Iterable<Key> keys(Key lo, Key hi) {
 91         if (lo == null) {
 92             throw new IllegalArgumentException("first argument to keys() is null");
 93         }
 94         if (hi == null) {
 95             throw new IllegalArgumentException("second argument to keys() is null");
 96         }
 97         Queue<Key> queue = new Queue<Key>();
 98         if (lo.compareTo(hi) > 0) {
 99             return queue;
100         }
101         for (int i = rank(lo); i < rank(hi); i++) {
102             queue.enqueue(keys[i]);
103         }
104         if (contains(hi)) {
105             queue.enqueue(keys[rank(hi)]);
106             //queue.enqueue(hi);
107         }
108         return queue;
109     }
110 
111     private boolean isEmpty() {
112         // TODO Auto-generated method stub
113         return n == 0;
114     }
115 
116     public void put(Key key, Value val) {
117         if (key == null) {
118             throw new IllegalArgumentException("first argument to put is null");
119         }
120         if (val == null) {
121             delete(key);
122             return;
123         }
124         int i = rank(key);
125         if (i < n && key.compareTo(keys[i]) == 0) {//已有元素進行更新
126             vals[i] = val;
127             return;
128         }
129         for (int j = n; j > i; j--)  {//鍵值對後移
130             keys[j] = keys[j-1];
131             vals[j] = vals[j-1];
132         }
133         keys[i] = key;
134         vals[i] = val;
135         n++;
136     }
137 
138     public void delete(Key key) {
139         if (key == null) {// 避免空指標錯誤
140             throw new IllegalArgumentException("argument to delete is null");
141         }
142         if (isEmpty()) {
143             return;
144         }
145         int i = rank(key);
146         if (i == n || key.compareTo(keys[i]) != 0) {
147             return;
148         }
149         for (int j = i; j < n - 1; j++) {//元素前移
150             keys[j] = keys[j + 1];
151             vals[j] = vals[j + 1];
152         }
153         n--;
154         keys[n] = null;
155         vals[n] = null;
156     }
157 }

 

 

 

 

我們預設使用的二分查詢是迭代進行,下面給出遞迴的形式:

 1     public int rank(Key key,int lo,int hi) {
 2         if(hi<lo) {
 3             return lo;
 4         }
 5         int mid=lo+(hi-lo)/2;
 6         int cmp=key.compareTo(keys[mid]);
 7         if(cmp<0) {
 8             return rank(key,lo,mid-1);
 9         }else  if(cmp>0) {
10             return rank(key,mid+1,hi);
11         }else {
12             return mid;
13         }
14     }

 

如果理解了迭代的形式,就能很容易改寫出遞迴了。

 

用物件的陣列代替兩個平行陣列(求指點):

定義一個以鍵和值為屬性的物件,用物件陣列來代替兩個平行陣列,由於本人學術不精,未能完成,主要問題是不知如何建立不同型別屬性的泛型陣列(key繼承了comparable而value繼承了object)。在此提出問題,希望高人指點!

 

 1 public class BSST<Key extends Comparable<Key>, Value> {
 2     class Item{//內部類
 3         Key key;
 4         Value val;
 5     }
 6     private Item[] item;
 7     private int n = 0;
 8     public BSST(int capacity) {// 動態調整大小
 9         item = (Item[]) new Object[capacity];//出現型別轉換錯誤
10     //item = (Item[]) new Comparable[capacity];//出現型別轉換錯誤
11
12 } 13 }

 

 

 

 

四、符號表的基本操作

符號表的基本操作遠遠不止本文提到的rank()、put()、get()、delete()。下面將他們列出來,瞭解個大概,在下一篇文章中將會一一實現他們。

boolean contains(Key key):判斷key是否存在於符號表中

int size(Key lo,Key hi):返回lo到hi之間的鍵值對數量

Key min():返回最小鍵

Key max():返回最大鍵

Key floor(Key key):返回小於等於key的最大鍵

Key ceiling(Key key):返回大於等於key的最小鍵

Key select(int k):返回排名為k的鍵

Iterable<Key> keys(Key lo,Key hi):返回一個佇列,包含lo-hi之間的所有鍵(已排序)

 

rank(select(k))==k ture

select(rank(key))==key ture