【问题标题】:Sorted set with fast lookup (as fast as HashSet?)具有快速查找的排序集(与 HashSet 一样快?)
【发布时间】:2014-09-15 08:02:33
【问题描述】:

我正在寻找某种可以同时满足这两个要求的集合数据结构:

  1. 已排序
  2. O(1) 查找

这是我目前得到的,但我真的希望有一个现有的、不那么尴尬的数据结构。

/**
 * This MUST support both 
 * (1) Looking up by A - O(n)
 * (2) Iteration by sorted Foo<A, B>
 */
public class MySet<Foo<A, B>> extends TreeSet<Foo<A, B>>
{ 
    private Map<A, Foo<A, B>> temp = new HashMap<A, Foo<A, B>>();

    public Foo<A, B> getNode(A a)
    {
       return temp.get(a);
    }

    @Override
    public boolean add(Foo<A, B> foo)
    {
       temp.put(foo.getA(), foo);
       return super.add(foo);
    }
}

而我的Foo 类看起来像这样:

public class Foo<A, B>
{
    private A a; //Can NEVER be null
    private B b; //Can NEVER be null

    //... constructor and stuff omitted 

    public int compareTo(Foo<A, B> that)
    {
        if (this.equals(that))
           return 0;

        //Compare by a first
        int ret = this.a.compareTo(that);
        if (ret == 0)
            return 0;

        //Compare by b
        return this.b.compareTo(that.b);            
    }

    public boolean equals(Object obj)
    {
        if (!(obj instanceof Foo))
            return false;

        Foo rhs = (Foo) obj;

        return this.a.equals(rhs.a) && this.b.equals(rhs.b);
    }
}

更新:

这是我的套装的一个用例:

MySet<Foo<SomeA, SomeB>> mySet = getTheData(); //getTheData() returns a set with a bunch of Foo objects

SomeA a = getA(); //getA() returns some instance of SomeA that I'm interested in

我希望能够检查集合并检索 Foo 对象(如果存在),这样 Foo.getA() == a;

mySet.getNode(a); 

【问题讨论】:

  • 为什么 O(log n) 查找不可接受?这已经相当快了。
  • 实际上,这是可以接受的,但是,我将如何从 TreeSet 中即时检索一个值?我希望能够通过 foo.getA() 进行查找,而不仅仅是检查树以查看元素是否在其中。
  • 你会使用 TreeMap。但是您需要通过A 查找和按Foo&lt;A, B&gt; 排序的迭代?那么无论如何都有两个结构没有好办法。 (您可以将一个哈希表和一个排序集混合在一起,但这会是很多讨厌的工作,而且可能效率不高)。
  • A 查找和按Foo&lt;A, B&gt; 排序的迭代正是我所需要的!是的,我认为这需要大量将多个数据结构混合在一起。
  • 好吧,如果您不经常插入,只需保留一个单独的哈希图和二叉搜索树。如果您经常插入,树集将成为瓶颈,您将不得不忍受每个操作的 Omega(log n)。

标签: java algorithm data-structures hashmap treemap


【解决方案1】:

您可以通过使用一些额外的空间来获得它。所以你需要一个HashSet。此外,每个元素将指向排序顺序中的下一个值。假设您有键 1、3、5、10,并且您正在使用线性探测。

value array   =  [3, 5, null, null, 10,   1];
pointer array =  [1, 4, null, null, null, 0];

所以值数组包含值。哈希函数决定值的去向。因此在上面的示例中,h(1) = 5(1 转到索引 5),h(3) = 0,h(5) = 1,并且 h(10) = 4。索引 2、3 具有 null (未来元素的开放空间)。指针数组表示在排序顺序中哪个元素跟在当前元素之后。因此,假设我们正在执行 set.contains(3),它将导致计算 h(3)(这将产生 0),并且我们知道该元素存在于集合中。如果我们想要根据排序顺序在元素集中的下一个元素,我们查看指针数组中的值。所以对于值3(在值数组中的位置0),我们通过查找指针数组中的索引(pointer_array[0],即1)得到排序顺序中的下一个元素,然后查找value_array [1],即 5。

这是一个非常常见的实现。 Java的LinkedHashMap通常用作LRU缓存,实现为哈希映射+键的双向链表。双向链表中的键按访问顺序排列。

在您的情况下,当您插入一个元素时,您需要调整非常慢的指针数组。您必须进行线性扫描。 如果这不是只读的,可以使用下面的方法

在您的数据结构中,有一个哈希集和一个 avl 树、一个红黑树或任何其他平衡二叉树。每当您进行 containsKey 测试时,它都是 O(1)。每当您枚举时,您都可以使用二叉树在线性时间中按排序顺序遍历它们。每当您插入一个新元素时,您也将它插入到二叉树和 HashSet 中。当你删除时,你从散列集和二叉树中删除元素。所以删除和插入变成了O(log n)

【讨论】:

    【解决方案2】:

    我认为您应该尝试在 google guava 库中使用 MultiMaps。 它的使用也很简单:

    Map<Salesperson, List<Sale>> map = new Hashmap<SalesPerson, List<Sale>>();
    public void makeSale(Salesperson salesPerson, Sale sale) {
        List<Sale> sales = map.get(salesPerson);
        if (sales == null) {
          sales = new ArrayList<Sale>();
          map.put(salesPerson, sales);
        }
        sales.add(sale);
    }
    

    可以替换为,

    Multimap<Salesperson, Sale> multimap = new ArrayListMultimap<Salesperson,Sale>();
    public void makeSale(Salesperson salesPerson, Sale sale) {
        multimap.put(salesperson, sale);
    }
    

    但是你必须在这里小心,multimaps 将保留具有相同键的条目,这与 hashmaps 不同,它用最新的键替换等效键。

    Google Guava 库具有许多其他具有不同功能的数据结构。您可以在其 wiki 上找到有关它的信息。

    希望这对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-30
      • 2020-08-14
      相关资源
      最近更新 更多