典型的应用程序中,键都是Comparable的对象,因此可以使用a.compare(b)来比较a和b两个键。许多符号表的实现都利用Comparable接口带来的键的有序性来更好地实现put()和get()方法。更重要的事在这些实现中,我们可以认为符号表都会保持键的有序并大大扩展它的API,根据键的相对位置定义更多实用的操作。例如,假设键是时间,你肯呢个会对最早或是最晚或是给定时间段内的所有键等感兴趣。在大多数情况下实现put()和get()方法背后的数据结构都不难实现这些操作。
只要你见到类的声明中含有泛型变量Key extends Comparable<Key>,那就说明这段程序是在实现这份API,其中的代码以来于Comparable的键并且实现了更加丰富的操作。上面所有的操作一起为用例定义了一个有序符号表。
对于一组有序的键,最自然的反应就是查询其中的最佳键和最小键。我们在讨论优先队列时已经遇到过这些操作。在有序符号表中,我们也有方法删除最大键和最小键(以及它们所关联的值)。有了这些,符号表就具有了类似于优先队列中的IndexMinPQ()的能力。主要的区别在于优先队列中可以存在重复的键但符号表中不行,而且有序符号表支持的操作更多。
对于给定的键,向下取整(floor)操作(找出小于等于该键的最大值)和向上取证(ceiling)操作(找出小于等于该键的最小值)有时是很有用的。这两个术语来自实数的取整函数(对一个实数向下取整即为小于等于x的最大整数,向上取证则为大于等于x的最小整数)。
检验一个新的键是否插入合适位置的基本操作是排名(rank,找出小于制定键的键的数量)和选择(select,找出排名为k的键)。要测试一下你是否完全理解了它们的作用,请确认对于0到size() – 1的所有i都有i == rank(select(i)),且所有的键都满足key == select(rank(key))。有序符号表的操作如下表所示。
给定范围内(在两个给定的键之间)有多少键?是哪些?在很多应用中能够回答这些问题并接受两个参数的size()和keys()方法都很有用,特别是在大型数据库中。能够处理这类查询是有序符号表在实践中被广泛应用的重要原因之一,
当一个方法需要返回一个键但表中却没有任何的键可以返回时,我们约定抛出一个异常(另一种合理的方法是在这种情况下返回空)。例如,在符号表为空时,min()、max()、deleteMin()、deleteMax()、floor()和ceiling()都会抛出异常,当k<0或k>=size()和select(k)也会抛出异常。
在基础API中文名已经见过了contain()和isEmpty()方法,为了用例的清晰我们又在API中添加了一些冗余的方法。于是我们约定所有符号表API的实现都含有如下所示方法。
方法 | 默认的实现 |
void deleteMix() | delete(min()); |
void deleteMax() | delete(max()); |
int size(Key lo, Key) | if (hi.compareTo(lo) < 0) return 0; else if (contains(hi)) return rank(hi) - rank(lo) + 1; else return rank(hi) = rank(lo); |
Iterable<Key> keys() | return keys(min(), max()); |
Java的一条最佳实践就是维护所有Comparable()类型中compareTo()方法和equals()方法的一致性。也就是说,任何一种Comparable类型的两个值a和b都要保(a.compareTo(b) == 0)和e.equals(b)的返回值相同。为了避免任何潜在的二义性,我们不会在有序符号表的实现中使用equals()方法。作为代替,我们只会使用compareTo()方法来表叫两个键,即我们用布尔表达式a.compareTo(b) == 0来表示“a和b相等吗?”。一般来说,这样的比较都代表这在符号表中的一次成功查找(找到了b)。和排序算法一样,Java为许多经常为键的数据结构提供了标准的compareTo()方法,为你自定义的数据类型实现一个compareTo()方法也不困难。
无论我们是使用equals()方法(对于符号表的键不是Comparable对象而言)还是Comparable()方法(对于符号表的键是Comparable对象而言),我们使用比较一词来表示将一个符号表条目和一个被查找的键进行比较操作。在大多数的符号表实现中,这个操作都出现在内循环。在少数的例外中,我们则会统计数组的访问次数。
查找的成本模型。在学习符号表的实现时,我们会统计比较的次数(等键性测试或是键的相互比较)。在内循环不进行(极少)的情况下,我们会统计数组的访问次数。
符号表实现的重点在于其中使用的数据结构和get()、put()方法。在用例代码中,除非我们想使用一个特定的实现,我们都是使用ST表示一个符号表实现。