字符串集实现

时间:2011-11-04 07:23:44

标签: algorithm data-structures hash

我必须为一对字符串实现一个ADT设置。我想要的接口是(在Java中):

public interface StringSet {
  void add(String a, String b);
  boolean contains(String a, String b);
  void remove(String a, String b);
}

数据访问模式具有以下属性:

  1. contains操作比addremove操作更频繁。
  2. 通常情况下,contains返回true,即搜索成功
  3. 我能想到的一个简单实现是使用两级哈希表,即HashMap<String, HashMap<String, Boolean>>。但是这种数据结构没有使用访问模式的两个特性。我想知道是否有比哈希表更有效的东西,可能是通过利用访问模式的特点。

4 个答案:

答案 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>。但我强烈建议使用提供的一组实现,HashSetTreeSet

答案 2 :(得分:0)

不要使用普通树(大多数标准库数据结构)。有一个简单的假设,在这种情况下会伤害你:

树上操作的正常O(log(n))计算假设比较在O(1)。这适用于整数和大多数其他键,但不适用于字符串。如果是字符串,则每个比较都在O(k)上,其中k是字符串的长度。这使得所有操作都取决于长度,如果您需要快速且很容易被忽视,这很可能会对您造成伤害。

特别是如果您经常返回true,则每个级别的每个字符串都会进行k比较,因此使用此访问模式,您将体验到树中字符串的完整缺陷。

Trie可以轻松处理您的访问模式。测试是否包含字符串是O(k)最坏的情况(不是哈希映射中的平均情况)。添加字符串也在O(k)中。由于你要存储我建议的两个字符串,你不是按字符索引你的trie,而是用一些更大的类型索引,所以你可以添加两个特殊的索引值。第一个字符串结尾的一个值,以及两个字符串结尾的一个值。

在你的情况下使用这两个额外的符号也可以简单删除:只需删除包含结束符号的最终节点,就不会再找到你的字符串了。您将浪费一些内存,因为您的结构中的字符串仍然已被删除。如果这是一个问题,您可以跟踪已删除的字符串的数量,并重建您的trie,以防这种情况变得糟糕。

P.S。 trie可以被认为是树和几个哈希表的组合,因此这为您提供了两种数据结构中的最佳结构。

答案 3 :(得分:-1)

  1. Red-Black Tree实施set将是一个不错的选择。 C++ STL已在Red-Black Tree
  2. 中实施
相关问题