实现电话通讯录 - 反向索引

时间:2009-09-22 17:54:01

标签: language-agnostic data-structures

现在我正在努力想出一个用于实现电话广告书的数据结构。 假设有两个信息:姓名,电话号码。同名可以有多个号码。 现在我想提出一个数据结构,它将帮助我获取给定名称的数字并获取名称,给出一个数字。我必须在最短的时间内完成这项工作。 我正在考虑维护一个地图(名称到数字)然后对于给定的数字以某种方式反向索引到这个地图然后找到名称。 我不确定最好的方法是什么。 你能建议一个理想的数据结构吗?将使用一些反向索引帮助。如果是,那么我如何在这种情况下使用它。

4 个答案:

答案 0 :(得分:1)

假设需要前缀匹配,我建议使用Patricia trie。假设名称和电话号码永远不会发生冲突 - 即您的目录中没有任何名为435-9876的人 - 那么您可以插入带有由trie索引的电话号码的对以及按名称索引的对。如果由于某些奇怪的原因,名称号码冲突是可能的,那么您可以添加一个特殊字符来为电话号码添加前缀,以便它们更长时间碰撞。

插入将花费O(s)
查找将花费O(s)
其中s是搜索字符串/插入密钥的长度。

此解决方案还允许您支持linux shell样式自动完成,您可以在其中确定与键入的特定名称匹配的条目数,或者甚至按需显示所有匹配项。

编辑:

patricia trie就像一棵普通的树,除了patricia trie中的分支只有在至少有两个不同的密钥共享一个未被前一个分支分开的前缀时才会出现。

  1. 内部节点指示键不同的索引,而叶节点存储实际键。
  2. 节点的所有子节点共享父节点的前缀。
  3. 来自节点的分支被“标记”,其字符出现在父母指定的位置。
  4. 子项不能指定比其父项小的索引。
  5. 因此,如果Jane和Jamie是存储在trie中的唯一键,它有三个节点 - 父表示共享前缀'Ja',一个分支指向包含所有以'Jan'开头的字符串的子树 - 从那时起只有一个存储在叶子节点“Jane”中,而另一个分支通向包含所有以“Jam”开头的字符串的子树,同样只有一个。

               [2]
       n                m
    'Jane'            'Jamie'
    

    如果你添加了詹姆斯,你就会得到

               [2]
       n                m
    'Jane'             [3]
                  e             i
                'James'       'Jamie'
    

    然后添加Jamelia

               [2]
       n                m
    'Jane'             [3]
                  e             i
                  [4]         'Jamie'
                 l     s
            'Jamelia'   'James'
    

    搜索很简单。从根开始,检查指定的索引。如果没有孩子被标记为指定的实际值,在我们的示例中,例如,如果正在搜索Jasper,因为位置2中的字符是s而不是n或m,搜索返回该值不存在。否则,遵循适当的分支,直到找到值,证明它不存在,或者保留多个匹配。例如。在标记为[3]的节点处搜索Jam停止。有几个节点匹配,但密钥Jam不存在。在这种情况下,您可以遍历以[3]为根的子树来构建部分匹配列表。请注意,这仅适用于您进行部分匹配的情况,如果指定了整个密钥,则搜索“Jam”的结果将不匹配。

    搜索的成本是按键长度 s 的顺序,因为你可以看到在找到密钥之前最多只有 s 级别或者表明不在那里。类似地,插入的成本也是要插入的密钥长度的顺序,因为插入是通过执行搜索,然后添加分支在树中最长前缀的节点。

    你可以找到一个java实现here以及看似是C ++实现的here

    我从来没有真正使用过我指向过的任何实现,所以不能保证它们的真实性。如果您决定自己实现数据结构,我建议您使用二进制字母表,这样会更容易! Here是一篇描述算法的文章。

答案 1 :(得分:1)

我会维护两个(哈希)地图。

第一张地图将名称作为键,而将(或列表或向量)设置为值,此列表将包含特定人员的所有电话号码。

Map<String,Set<String>> NamesToNumbers;

第二张地图将电话号码作为键,名称作为值。

Map<String,String> NumbersToNames;

此外,我将创建一个简单的类,将这两个映射作为私有成员,此类的目的是使两个映射保持同步,并将所有put(),remove()等调用传递给两个映射以正确的键/值格式。

psuedo代码,用于在简单类中编写put()方法:

public void put(String Name,String PhoneNumber)
{

Set NumbersForName = NamesToNumbers.get(Name);
if (NumbersForName == null)
  {
    NumbersForName = new Set();
    NamesToNumbers.put(Name,NumbersForName);
  }

NumbersForName.put(PhoneNumber);
NumbersToNames.put(PhoneNumber,Name);  

}

查找的成本将为O(1),并且插入的成本将为O(1),除非存在哈希冲突。

如果你使用的是Java 5+,你应该看看Google Collections,他们有一个漂亮的Bi-map类,这本质上就是我想要描述的。

答案 2 :(得分:1)

我建立了一个名为x Number对的概念数据库。每一对都有一个id。除非你能保证约翰史密斯不会住在两个不同的地方,否则你需要一个独特的身份证。

所以你有(在perl中)

  $db{$id} = [name, number]

然后用两个返回id组的哈希重叠。

$name_index[$name] = [$id1, $id2, $id3]
$number_index[$number] = #as above

这需要一定程度的繁琐更新,但会快速返回结果。

您可以使用具有哈希/地图构造的任何语言执行此操作。

答案 3 :(得分:0)

您可以使用一对字典(也称为地图):一个用于使用名称(name -> List<number>)查找数字,另一个用于使用数字查找名称(number -> name)。