【问题标题】:Most efficient way to search multi-tree data structure搜索多树数据结构的最有效方法
【发布时间】:2013-06-21 19:07:18
【问题描述】:

我想知道是否有比我一直在做的更有效的方法来搜索多树。我最近为一个项目构建了一个多树数据结构,如下图所示。

洞穴会将各方放在一个数组列表中。不同的政党将拥有不同的生物。不同的生物会持有不同的物品。

我需要一种方法来搜索整个树,以将对象与它具有的属性(例如索引)进行匹配。这是我的程序的一个 sn-p,它搜索每个 ArrayList 以查看是否有一方、生物、宝藏或神器与我称为 index 的 int 匹配。

编辑(解释我的代码) Party、Creature、Treasure 和 Artifact 类都具有索引、名称、类型等属性。多树将 Cave 集作为根注释。 Cave 有一个 ArrayList 可以包含许多 Party 对象。每个 Party 都有一个 ArrayList,可以包含许多 Creature 对象。每个生物都有两个数组列表,一个用于保存神器对象,一个用于保存宝藏对象。

我在下面搜索以查看哪个派对、生物、神器或宝藏拥有我正在搜索的特定索引。我通过迭代派对来做到这一点,每个派对我都查看生物,每个生物我查看文物和宝藏。这就是为什么我在 for 循环中有这么多 for 循环 :(.

case 0 :
            int index =                 Integer.parseInt( stat );
            for ( Party p : SorcerersCave.theCave.parties ) {
                if ( index == p.getIndex()) {
                    generateInterface.theGame.printOutput( "\t" + p );
                    break;
                } else {
                    for ( Creature c : p.members ){
                        if ( index == c.getIndex() ){
                            generateInterface.theGame.printOutput( "\t" + c );
                            break;
                        } else {
                            for ( Treasure t : c.inventory ){
                                if ( index == t.getIndex() ){
                                    generateInterface.theGame.printOutput( "\t" + t );
                                    break;
                                }
                            }
                            for ( Artifact a : c.artifacts ){
                                if ( index == a.getIndex() ){
                                    generateInterface.theGame.printOutput( "\t" + a );
                                    break;
                                }
                            }
                        }
                    }
                }
            }

我觉得这段代码太复杂,难以理解。该代码有效,但它对我原本非常好看的代码来说是一个丑陋的污点。我一直在寻找更好的方法来做到这一点,甚至是改进它的方法。

注意* 项目要求禁止我们将每个对象放在同一个 ArrayList 中。

【问题讨论】:

  • 我敢打赌圈复杂度分析器会喜欢这段代码...说真的,让我们避开Java代码并在纸上列举算法的目标。如果你能做到这一点,那么你可以找出一个稍微干净的方法/方法来解决这个问题。实际上,我没有看到任何“树”——事实上,您提供的代码 sn-p 不足以给您一个好的答案。您能否为此处的代码提供一些上下文,例如您要施加的规则是什么?
  • 我不知道哪里有工具可以告诉你你的代码有多复杂!迫不及待想试试。那就是说我已经在纸上完成了这个,除了像上面那样循环之外我看不到其他方法。我是一个相对年轻的程序员,所以我仍在尝试构建我的工具集和知识。
  • 如果 getIndex 是所有类的通用方法,您会考虑将其抽象为超类吗?如果您绝对需要,最后一种方法可能是使用 instaceof 或反射。
  • 我认为break只会跳出内循环。
  • @isaach1000: break 将在那个级别跳出循环。更高级别的break 语句将跳出更高级别的循环。

标签: java search optimization arraylist


【解决方案1】:

您的类可以实现一个公共接口SearchableByIndex,它看起来像这样,并且将由树中的每个类实现:

public interface SearchableByIndex {
    public SearchableByIndex searchByIndex(int index);
    public int getIndex();
}

那么CavePartyCreature需要这个代码:

public SearchableByIndex searchByIndex(int index) {
    if (getIndex() == index) {
        return this;
    } else {
        for (Party party : parties) {    // or Creature creature : members etc.
            SearchableByIndex found = party.searchByIndex(index);
            if (found != null) {
                return found;
            }
        }
    }
    return null;
}

宝藏和神器的版本:

public SearchableByIndex searchByIndex(int index) {
    return (getIndex() == index) ? this : null;
}

而不是你的嵌套循环,你会有

case 0:
    int index = Integer.parseInt(stat);
    SearchableByIndex foundItem = SorcerersCave.theCave.searchByIndex(index);
    if (foundItem != null) {
        generateInterface.theGame.printOutput("\t" + foundItem);
    } else {
        // print out that the item was not found
    }
    break;

虽然每个类上的 searchByIndex() 方法可能看起来重复,但它是 OOP 的方法。这样,每个类都会负责搜索其所有内容。在您的情况下,所有类的代码都非常相似(唯一的变化是带有 for 循环的行),但您可以认为这是一个意外 - 任何类都可以包含它想要的任何数据,并且没有其他类应该关心关于它如何存储数据。

将来,如果您的某个类包含更多可搜索数据(TreasureArtifactHostage?),或者您想更改其数据结构(考虑 HashMap,如果你可以!),你只需要更改特定类中的searchByIndex() 方法。

此外,只有包含数据的类才应该知道如何包含它。您现在拥有它的方式,调用搜索的其他一些类确切地知道数据是如何存储在每个类中的——这不应该是。如果一个类发生变化,它会破坏其他类的内部代码。例如,Cave 类应在其文档中声明它拥有例如不同的Party 实例,如果您通过searchForIndex() 搜索它,它可以给出其中的一些实例。

【讨论】:

  • @Slanec- 无论如何你可以给我发电子邮件吗?我有几个关于 HashMap 的问题,我觉得这些问题超出了这个问题的范围。我的电子邮件显示在我的个人资料中。
  • @Dustin 电子邮件是私人的,所以我看不到你的。给我发电子邮件> JanecekPetr squigglyAtSign seznam.cz
【解决方案2】:

项目要求禁止我们将每个对象放在同一个 ArrayList 中。

(据推测,HashMapTreeMap 也被禁止...)

如果没有某种辅助数据结构来将index 值映射到它们所引用的对象,您就别无选择只能进行树遍历。因此,平均而言,如果有 N 对象,您的“查找对象”代码将是 O(N)……具有相当大的比例常数。

(大概,对问题施加限制的人并不关心性能。你应该采取同样的态度......尤其是考虑到你的 cmets 中列出的上下文。)

您可以通过几种方式改进代码,使其更具可读性和(很可能)更高效。

  1. 使用“break to label”,这样当内部循环被命中时,它们会退出到外部循环的末尾。 (假设这就是你想要的。)

  2. 使用临时变量来避免每次在容器层次结构中查找元素时评估多个方法调用。 更新:看起来你刚刚重写了代码来做到这一点......


...项目要求规定每个对象都需要在多树数据结构中实例化,如我发布的图表所示。

但他们是否明确禁止二级数据结构?还是您只是假设不允许您这样做?

【讨论】:

  • 项目要求禁止有二级数据结构。我问了教授同样的问题,他的回答是在他的回答中输入了 34 次“不”,没有任何解释。
  • @Dustin 我打赌你会在下个学期学习,但如果你好奇并且想在今天(再次)学习一些新的和更难的东西,看看Hash maps 的能力。这个想法是您将拥有一个包含树的所有元素的HashMap,其中index 将是键。这样,您可以在一行代码中立即搜索整个树 (O(1)),而不是在嵌套的 for 循环和/或方法调用中线性搜索 (O(n))。最好的数据结构总是取决于它的用途。
  • @Dustin - 他对你说,对于这个练习,“效率并不重要”。这是一个编程练习,而不是构建世界下一个杀手级 D&D 应用程序(或其他)的项目。做练习......然后继续前进。
  • 其实这里有一个重要的教训。在现实世界中,经常效率不是软件开发项目的重要标准。代码工作、满足功能要求、可维护和按时交付通常更为重要。如果性能不重要,你不应该通过优化浪费你的时间(和老板的钱)。
【解决方案3】:

我会改用 for-each 循环:

int index = Integer.parseInt(stat);
for(Party party : parties){
    if( index == party.members.get( c ).getIndex() ){
        generateInterface.theGame.printOutput( "\t" + party.summary );
        break;
    } else {
        for(Creature creature : party.Creatures){
            // etc etc.....
        }
    }
}

这将使代码更易于阅读。它的缺点是循环中没有内置计数器。

编辑:这篇文章可能会澄清: https://blogs.oracle.com/CoreJavaTechTips/entry/using_enhanced_for_loops_with

【讨论】:

  • 我不知道 for-each 结构。我对此唯一的问题是,如果我有多个队伍,每个队伍都有不同的生物,这将如何搜索每个队伍的生物?还有每个生物的宝物和神器?
  • 请参阅:blogs.oracle.com/CoreJavaTechTips/entry/… 这可能会有所帮助。
【解决方案4】:

这是一个非常奇怪的约束集,但是本着树遍历的精神,根本不需要修改原始数据结构,为什么不把朴素的递归遍历函数写成一组重载方法呢?它可能看起来像这样:

public Object search(int index, Cave c) {
  if (c.id == index) return c;

  for (Party p : c.parties) {
    Object result = search(index, p);
    if (result != null) 
      return result;
  }

  return null;
}

public Object search(int index, Party p) {
  ...
}

等等。

【讨论】:

  • 随着 OP 所做的改变(for-each 循环而不是通常的索引循环),它大声喊叫这样的递归。
  • 确实如此。这通常作为遍历树的教科书方法。
【解决方案5】:

你能使用更规则的结构吗?这些方面的东西(我正在使用伪代码):

interface INode { ... stuff kept in nodes is described here ... }
interface ITree extends INode { bool HasId(int id); ITree[] Children; }

INode SearchTreeForId(ITree[] children, int id) {
  for each (child in children) {
    match = (child.HasId(id) ? child : SearchTreeForId(child.Children));
    if (match != null) return match;
  }
  return null;
}

// Then...
{
  node = SearchTreeForId(SorcerersCave.theCave.Parties, id);
  if (node != null) generateInterface(...);
}

【讨论】:

  • 我希望但遗憾的是不,项目要求要求每个对象都需要在多树数据结构中实例化,如我发布的图表所示
  • 嗨达斯汀,我不确定多树是什么——也许是树的集合? ITree 接口提供了这种结构。无论如何,祝你的程序好运。
  • 根据我的教授的说法,多树是一个数据结构,每个“节点”即。洞穴、派对、生物等。可以有 0
  • 啊,我明白了。请注意,ITree 接口指定了一个 array 子级,而不是一个 pair 子级。这在您的教授的术语中表征了“多树”。
【解决方案6】:

使用辅助方法进行深度优先搜索。

private Interface IndexObject
{
    // Interface will be used in dfsList
    public int getIndex();
}

private Interface ListObject extends IndexObject
{
    // For classes with sublists
    public ArrayList<IndexObject> getList();
}

public void search(int index) {
    Object found = dfsList(parties, index);
}

// Helper method
public Object dfsList(ArrayList<IndexObject> aList, int index) {
    for (IndexObject obj : aList) {
        if (obj.getIndex() == index) {
            return obj;
        }
        else if (obj instanceof ListObject) {
            IndexObject found = dfsList(obj.getList(), index);
            if (found != null) {
                return found;
            }
        }
    }
    return null;
}

【讨论】:

    【解决方案7】:

    由于您的入口点是 Cave object ,因此可以考虑制作一个唯一的字符串,例如代表整个 Cave 对象(Cave、Part、Creatures、Treasures、Artifacts)的主键,例如连接您想要的那些字段搜索 例如,如果您想按索引 3 搜索工件,您可以创建一个类似

    的字符串
    C-P-C-T/A 
    

    每个字母代表 'this' 类中的唯一索引,所以在这种情况下可能是这样的

    3-4-32-A3 
    

    其中 3 代表第 3 个洞穴,4 代表第 4 方....直到 A3 代表第 3 个神器。

    希望您找到比这更好的解决方案,如果请分享。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多