【问题标题】:Algorithm/Data structure to rank elements in a tree对树中的元素进行排序的算法/数据结构
【发布时间】:2011-05-24 23:59:47
【问题描述】:

这就是我所拥有的:一棵具有任意层数的树。我需要一种方法来对每个级别的每个级别的所有节点进行排名。如果不清楚,假设我的第一个级别是世界。我的第二个层次是大陆。我的第三个层次是国家。我的第四层是城市。每个国家都有一个城市列表,按人口顺序排列。每个大陆都有一个按人口排列的国家列表。每个大陆也有一个按人口排名的城市列表。等等。

我想象的算法是非常简单的递归,但我不确定跟踪这些列表的最佳数据结构是什么。每个级别不知道它有多少个子级别,所以我不能声明任意数量的列表。

有什么想法吗?

这里有一些示例代码:

public void calcStats()
    {
        initWorldRanks();//clears ranks for the world
        for(Entity continent:theWorld.getChildren())
        {
            initContinentRanks();//clears ranks for the continent
            for(Entity country:continent.getChildren())
            {
                initCountryRanks();//clears ranks for the country
                for(Entity city:country.getChildren())
                {
                                    //Assume that add preserves sorted order.  Sorting is easy.  The tricky part is that each entity needs to be added to its ancestors.  I don't want to have fixed data structures
                    worldCityRanks.add(city);
                    continentCityRanks.add(city);
                    countryCityRanks.add(city);
                }
                worldCountryRanks.add(country);
                            continentCountryRanks.add(country);
            }
            worldContinentRanks.add(continent);
        }

一切都正确排名,但这将我限制在明确的 4 级结构中。

【问题讨论】:

    标签: java algorithm sorting data-structures collections


    【解决方案1】:

    关键是您不想通过遍历每个节点的整个子树来重新计算每个节点的计数。缓存每个节点中的总计数。然后每个节点只需要从其子节点收集值来计算自己的总数(它也应该缓存)。

    你没有说这些节点是否可变。如果它们是不可变的,那么就很容易:在构造时添加所有子节点时,您可以构造一个节点的总数。

    如果它们是可变的,您可以让每个节点在其计数发生变化时告知其父节点。父级可以更新自己的计数并告诉其父级,依此类推。这使得更新计数 O(树的深度)或大致 O(logn)(取决于您的树的平衡程度)。

    对于实际对每个节点的子节点进行排序,请执行您通常会做的任何事情:使用 ArrayList 并对其进行排序,或者使用某种保持排序顺序的排序集合(例如:TreeSet,但请确保区分具有相同总体的元素)。重要的是,在比较时,您只会查看直接子代的值(即缓存的总和),而不是间接子代。

    更新

    根据您对问题的更新,您的问题之一是您有不同的方法来添加不同级别的内容。即:worldCityRanks.addcontinentCityRanks.addcountryCityRanks.add 等。您应该将这些全部替换为以深度为参数的单一方法。例如:

    // Probably in your Entity class
    public void addDescendant(int distance, Entity descendant) {
      // this replaces worldCityRanks.add, continentCityRanks.add,
      // countryCityRanks.add, etc.
    }
    

    然后,您的后代集合不再有 4 个字段,而是有一个集合(可能是 ArrayList)来保存它们。您可以根据需要扩展它。

    另一个问题是这些硬编码嵌套的 for 循环。要处理任意(在合理范围内)深度,最简单的方法是使用递归。例如:

    public void calcStats() {
      theWorld.initAllRanks();
      List<Entity> ancestors = new ArrayList<Entity>();
      theWorld.accumulateAllRanks(ancestors);
    }
    
    class Entity ... {
      ...
    
      void initAllRanks() {
        initRanks();
        for(Entity child: getChildren()) {
          child.initAllRanks();
        }
      }
    
      void accumulateAllRanks(List<Entity> ancestors) {
        int distance = ancestors.size();
        for(Entity ancestor: ancestors) {
          distance--;
          ancestor.addDescendant(distance, this);
        }
        ancestors.add(this); // push this
        for(Entity child: getChildren()) {
          child.accumulateAllRanks(ancestors);
        }
        ancestors.remove(ancestors.size() - 1); // pop this
      }
    

    这是假设您确实想要存储每个级别的排名(这是您的代码示例所暗示的)。这种方法使查找速度更快,但它会使更新速度变慢,而且它也比其他一些方法消耗更多的内存。特别是,您可以只维护全局排名列表,然后在查询时过滤这些列表。同样,这会使更新更快并消耗更少的内存,但会使查询比您当前使用的方法更慢。

    【讨论】:

    • 我已经想通了。随着子节点的添加,每个节点都会更新它的计数。计数向上传播。那部分很明显。我想弄清楚的部分是,存储所有这些排名的最佳方式是什么?
    • @JPC:我刚刚在您的评论中添加了最后一段。一旦您在每个节点中缓存了子树大小,您就不再需要将其视为一棵树。在每个级别,您都刚刚获得了您的直系子级的集合。如果我需要非常不频繁地重新排序(例如:对于不可变的情况),我可能会使用List + Collections.sort,如果我需要通过许多更新来维护排序顺序,我会使用TreeSet
    • 我在帖子中添加了一些示例代码以帮助澄清。排序部分很简单,所以我并没有真正将它包含在我的示例代码中。我们可以假设 add 保留排序顺序,或者我可以在循环结束时添加排序。问题是它被固定在一定数量的水平上。如何抽象我所写的内容以允许任意级别?
    最近更新 更多