我必须为一对字符串实现一个ADT设置。我想要的接口是(在Java中):
public interface StringSet {
void add(String a, String b);
boolean contains(String a, String b);
void remove(String a, String b);
}
数据访问模式具有以下属性:
contains
操作比add
和remove
操作更频繁。contains
返回true
,即搜索成功我能想到的一个简单实现是使用两级哈希表,即HashMap<String, HashMap<String, Boolean>>
。但是这种数据结构没有使用访问模式的两个特性。我想知道是否有比哈希表更有效的东西,可能是通过利用访问模式的特点。
答案 0 :(得分:3)
就我个人而言,我会根据标准Set<>
界面来设计:
public class StringPair {
public StringPair(String a, String b) {
a_ = a;
b_ = b;
hash_ = (a_ + b_).hashCode();
}
public boolean equals(StringPair pair) {
return (a_.equals(pair.a_) && b_.equals(pair.b_));
}
@Override
public boolean equals(Object obj) {
if (obj instanceof StringPair) {
return equals((StringPair) obj);
}
return false;
}
@Override
public int hashCode() {
return hash_;
}
private String a_;
private String b_;
private int hash_;
}
public class StringSetImpl implements StringSet {
public StringSetImpl(SetFactory factory) {
pair_set_ = factory.createSet<StringPair>();
}
// ...
private Set<StringPair> pair_set_ = null;
}
然后您可以将其留给StringSetImpl的用户以使用首选的Set类型。但是,如果您尝试优化访问权限,则很难做得比HashSet&lt;&gt;更好(至少在运行时复杂性方面),假设访问是O(1),而基于树的集合具有O(log N)访问时间。
包含()通常返回true可能使得值得考虑Bloom filter,尽管这需要允许包含()的一些误报(不知道是否是这种情况)。
修改
为了避免额外的分配,您可以执行类似这样的操作,这与您的两级方法类似,除了使用集合而不是第二级的映射:
public class StringSetImpl implements StringSet {
public StringSetImpl() {
elements_ = new HashMap<String, Set<String>>();
}
public boolean contains(String a, String b) {
if (!elements_.containsKey(a)) {
return false;
}
Set<String> set = elements_.get(a);
if (set == null) {
return false;
}
return set.contains(b);
}
public void add(String a, String b) {
if (!elements_.containsKey(a) || elements_.get(a) == null) {
elements_.put(a, new HashSet<String>());
}
elements_.get(a).add(b);
}
public void remove(String a, String b) {
if (!elements_.containsKey(a)) {
return;
}
HashSet<String> set = elements_.get(a);
if (set == null) {
elements_.remove(a);
return a;
}
set.remove(b);
if (set.empty()) {
elements_.remove(a);
}
}
private Map<String, Set<String>> elements_ = null;
}
因为我在上午4:20找到了,所以上面绝对不是我最好的作品(太累了,不能通过这些不同的集合类型刷新自己对null的处理),但它草拟了这种方法。
答案 1 :(得分:0)
我认为Michael Aaron Safyan使用StringPair
类型的方法。也许使用更具体的名称,或者作为通用元组类型:Tuple<A,B>
实例化为Tuple<String,String>
。但我强烈建议使用提供的一组实现,HashSet
或TreeSet
。
答案 2 :(得分:0)
不要使用普通树(大多数标准库数据结构)。有一个简单的假设,在这种情况下会伤害你:
树上操作的正常O(log(n))
计算假设比较在O(1)
。这适用于整数和大多数其他键,但不适用于字符串。如果是字符串,则每个比较都在O(k)
上,其中k
是字符串的长度。这使得所有操作都取决于长度,如果您需要快速且很容易被忽视,这很可能会对您造成伤害。
特别是如果您经常返回true,则每个级别的每个字符串都会进行k
比较,因此使用此访问模式,您将体验到树中字符串的完整缺陷。
Trie可以轻松处理您的访问模式。测试是否包含字符串是O(k)
最坏的情况(不是哈希映射中的平均情况)。添加字符串也在O(k)
中。由于你要存储我建议的两个字符串,你不是按字符索引你的trie,而是用一些更大的类型索引,所以你可以添加两个特殊的索引值。第一个字符串结尾的一个值,以及两个字符串结尾的一个值。
在你的情况下使用这两个额外的符号也可以简单删除:只需删除包含结束符号的最终节点,就不会再找到你的字符串了。您将浪费一些内存,因为您的结构中的字符串仍然已被删除。如果这是一个问题,您可以跟踪已删除的字符串的数量,并重建您的trie,以防这种情况变得糟糕。
P.S。 trie可以被认为是树和几个哈希表的组合,因此这为您提供了两种数据结构中的最佳结构。
答案 3 :(得分:-1)
Red-Black Tree
实施set
将是一个不错的选择。 C++ STL
已在Red-Black Tree