【问题标题】:Custom data structure (element + weight)自定义数据结构(元素+权重)
【发布时间】:2017-05-03 08:36:41
【问题描述】:

我应该使用哪种数据结构来保持按给定权重排序的元素?我需要在每个元素生成特定权重的集合中添加元素,但该权重不包含(未计算)在元素本身内;它是由元素之外的其他人计算的。此外,权重不需要存储(但如果需要也可以),它只是一个插入参数,用于将元素放置在正确的位置。

我的具体用例是对音乐曲目进行排序。我有一个曲目列表和一个适用于每个曲目的规则列表。我遍历每个轨道,然后规则列表在给定当前轨道的情况下生成一个“分数”。给定生成的分数,我想将曲目添加到“集合”中,这样​​我就可以选择得分最高的第一首曲目。

我看不到在 Java 中使用哪种数据结构。我必须自己实施一个新的吗?我最好的猜测是一种堆数据结构,但我不知道具体是哪一种。

编辑: 重量在开始时只计算一次。我无法提供“比较器”,因为它将比较曲目,并且我无法在“compareTo 方法”上计算它们的分数。 该算法可能如下所示:

 public Track determineNext() {
     MyStructure<Track> trackScores;
        for (Track track : tracks) {
            int score = 0;

            for (Rule rule : rules) {
                score += rule.applyScoreOn(track);
            }

            trackScores.add(track, score);
        }
     return trackScores.first();
 }

希望你能明白。

【问题讨论】:

  • “重量”多久计算一次?能改变吗?如果是,什么时候重新计算?
  • 如果权重不会改变,SortedSet: docs.oracle.com/javase/7/docs/api/java/util/SortedSet.html 可能是你需要的,但它取决于如何访问数据。
  • 你可以使用计算得到的分数forcompareTo() 方法吗?这看起来完全地类似于实现 PriorityQueue 的那种东西。

标签: java data-structures heap


【解决方案1】:

在我看来,PriorityQueue 正是您所需要的:

优先级队列的元素按照它们的自然顺序排序,或者由队列构建时提供的 Comparator 排序,具体取决于使用的构造函数。

在这种情况下,自然排序将是按分数。很简单。

至于你的观点:

我无法提供“比较器”,因为它会比较曲目,而且我无法通过“compareTo 方法”计算它们的分数。

这里的关键是不要使用compareTo() 方法来计算分数。这是相反的方式。你在compareTo()的实现中使用分数。


你有几种方法来实现它:

  1. 制作Track implements Comparable&lt;Track&gt;,并实现compareTo(Track t)比较你得分的分数,所以更高的分数-->“更大”的对象。 PriorityQueue 将负责其余的工作。
  2. 实现一个Comparator&lt;Track&gt;,它与compareTo() 对选项1 的作用基本相同。

如果您稍后在程序中使用不同的标准进行排序,选项 2 会更好,因为您不会承诺让 Tracks 仅按分数排序。

理想情况下,对于这两个选项,您都可以将分数存储在Track 中,因为分数只需要计算一次;但是,您必须确保在插入PriorityQueue 之前设置此分数。不过这应该不是问题。

或者,可以在比较方法中即时计算分数,如果您知道分数不会随时间变化;但是,如果必须进行大量比较,这可能会随着时间的推移而变得有些昂贵。如果你知道分数不会改变,我不会推荐这个。


或者,如果Track 对象不知道他们的分数(这是有道理的),您可以使用MapTracks 作为键和分数作为值来将Track 与分数相关联:

HashMap<Track, Integer> scoreMap = new HashMap<>();
for (Track track : tracks) { // Copied from question, slightly altered
    int score = 0;

    for (Rule rule : rules) {
        score += rule.applyScoreOn(track);
    }

    scoreMap.put(track, Integer.valueOf(score));

现在您可以使用比较器从该地图中提取分数

public class TrackComparator implements Comparator<Track> {
    private final Map<Track, Integer> scores;

    public TrackComparator(Map<Track, Integer> scores) {
        this.scores = scores;
    }

    @Override
    public int compare(Track t1, Track t2) {
        // Check to see if tracks are in map if you didn't pre-fill map
        // Get scores, compare, return
    }
}

不过有一些注意事项:

  • Maps 通常要求您定义额外的方法(hashCode() 除了用于equals()HashMap,以及用于TreeMap 的某种形式的比较器)
  • 地图的键不应该是可变的,尽管对于 Track 对象我看不出这是一个问题
  • 你得到另一个集合来吃掉内存。但是,如果您的分数在 [-128, 127] 范围内,那应该不是什么大问题。也可以传递 VM 标志来扩展此范围。我不会太担心内存消耗,除非您有大量的曲目或几张不同的地图浮动。
  • 仅在外部更改映射的值不会重新排序优先级队列。您必须删除()和添加()“分数”发生变化的对象才能移动到队列中的新位置。

我不太确定Map 解决方案是最好的 解决方案,但我认为远非最差,而且它本身应该相当不错。

一个有助于解决内存问题的可能更改是将Map 放在Comparator 中,如果需要,让比较器自己计算分数,如果轨道不存在,则将它们添加到地图中。这取决于您是否关心某些分数比其他分数晚“冻结”。这可能会对性能产生不利影响,但这是一个很大的猜测。

【讨论】:

  • 我有一个知道轨道集合和规则列表的类。理想情况下,我希望 Track 对分数一无所知。另外,我知道我可能有一个非常大的曲目集合和一个预定义的规则列表(比如说 15 个)。我想每次计算算法只计算一次分数(理想情况下),我担心 PriorityQueue 在同一个元素上不止一次(也许不是?)。我不想误导信息,但是每次计算算法时分数可能会给出不同的结果,但会在开始时计算一次。
  • @MiniW 嗯...如果Track 不应该知道任何关于分数的事情,也许使用MapTrack 与代表分数的Integer/Long 相关联?
  • 另外,是否对每个元素多次计算分数取决于您选择实现比较方法的方式。 PriorityQueue 几乎肯定会在一个元素上多次调用比较方法,但如果你缓存一个不应该有问题的值。
  • 这是我的一个想法,但我想知道是否有为这种事情设计的现有数据结构。
  • @MiniW 在Tracks 不知道分数的情况下更新了答案。
猜你喜欢
  • 1970-01-01
  • 2014-08-11
  • 1970-01-01
  • 2015-08-07
  • 1970-01-01
  • 1970-01-01
  • 2019-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多