【问题标题】:Object is not getting removed from ArrayList [duplicate]对象未从 ArrayList 中删除 [重复]
【发布时间】:2020-12-17 22:50:57
【问题描述】:

有一个通用的树数据结构。现在,它的 Node Class 看起来像这样。

private static class Node {
    int data;
    ArrayList<Node> children = new ArrayList<>();
  }

DS 的形成完全是通过输入来构建的。现在我需要执行的是删除所有叶节点。下面的代码无法删除已经存在于其实例成员中的子代。

public static void removeLeaves(Node node) {
ArrayList<Node> nodeChildrenList = node.children;
int childrenSize = nodeChildrenList.size();
for(int i = 0; i < childrenSize; i++){
    Node child = nodeChildrenList.get(i);
    removeLeaves(child);
    if(child.children.size() == 0){
        child.children.remove(child); // Problem
    }
}
}

我无法理解对象(节点)子节点没有从 ArrayList 中删除。但迭代器工作正常。我什至在每一步都尝试过调试。当断点到达 child.children.remove(child);线。它没有完成它。

虽然这段代码工作起来很迷人。

    ArrayList<Node> nodeChildrenList = node.children;
    // int childrenSize = nodeChildrenList.size();
    Iterator itr = nodeChildrenList.iterator();
    while(itr.hasNext()){
       Node child = (Node)itr.next();
       if(child.children.size() == 0){
           itr.remove();
       }
       removeLeaves(child);
    }
    // for(int i = 0; i < childrenSize; i++){
    //     Node child = nodeChildrenList.get(i);
    //     removeLeaves(child);
    //     if(child.children.size() == 0){
    //         child.children.remove(child);
    //     }
    // }
  }

【问题讨论】:

  • 这能回答你的问题吗? Delete data from ArrayList with a For-loop
  • 一方面,当 child.children 数组列表的大小为 0 时,您的 for 循环正在调用 child.children.remove(child)。它已经为空。另一方面,使用迭代器就地修改,而不是 for-each 循环。后者可以产生ConcurrenctModificationException 或更糟的非确定性行为。
  • 也许你的意思是if (child.children.size() &gt; 0)
  • 为什么要在列表为空时从列表中删除某些内容?为什么要从节点的子节点列表中删除节点本身?
  • @NomadMaker 我打赌OP实际上意味着如果孩子没有孩子,则将孩子本身从其父母中移除,即if (child.children.isEmpty()) { nodeChildrenList.remove(child); } ---但是,递归调用没有任何意义,因为该方法最终会删除 所有 个后代节点。

标签: java loops arraylist collections


【解决方案1】:

让我们看看你的 removeLeaves 和 for 循环在做什么。在下方添加了 cmets

ArrayList<Node> nodeChildrenList = node.children;
int childrenSize = nodeChildrenList.size();
for(int i = 0; i < childrenSize; i++){    // Loop through each of node's children
    Node child = nodeChildrenList.get(i); // Get the child of node at index i
    removeLeaves(child);                  // recursively call removeLeaves with the child
    if(child.children.size() == 0){       // If the node's child has no children
        // This is a problem because you are telling the program to remove the node's 
        // child from the node's child's children, not from the node's children.
        child.children.remove(child);     
    }
}

希望这是有道理的,您在错误的列表中调用 remove。要执行您想要的操作,您需要调用 node.children.remove(child),因为您想从节点的子节点列表中删除该节点的子节点。

但是,这也会导致问题,因为现在您已经更改了循环的结构。例如,让我们看一个非常简单的案例。

         1
        / \
       2   3

在这种情况下,我们有一个值为 1 的根节点和两个子节点。 第一个孩子的值为 2,没有孩子。 第二个孩子的值为 3,没有孩子。

因此,如果我们逐行遍历removeLeaves 方法,让我们看看会发生什么。

使用根节点调用时:

ArrayList<Node> nodeChildrenList = node.children; // nodeChildrenList contains two nodes - the ones with values 2 and 3

int childrenSize = nodeChildrenList.size(); // childrenSize is 2
for(int i = 0; i < childrenSize; i++){  // Start with i = 0
    Node child = nodeChildrenList.get(i); // The first child is the one with value 2
    removeLeaves(child);  // Call remove leaves again with the child with value 2

我们稍后会回到该方法的迭代,现在我们再次深入研究第一个孩子

ArrayList<Node> nodeChildrenList = node.children; // The first node has no children, so this is an empty list
int childrenSize = nodeChildrenList.size(); // size is 0
for(int i = 0; i < childrenSize; i++){  // None of this gets called
    Node child = nodeChildrenList.get(i);
    removeLeaves(child);
    if(child.children.size() == 0){
        node.children.remove(child); 
    }
}

现在我们已经退出了第一个子节点(值为 2),我们结束了根节点的循环迭代

    if(child.children.size() == 0){ // child is still the first child (value 2), and it doesn't have any children, so this is true
        node.children.remove(child); // So we remove it from the root node's children. 
    }
}

现在我们有了一个带有一个孩子的根节点(值为 3 的那个)很好,对吧?好吧,没有那么多。由于我们已经修改了循环遍历的结构,我们会遇到一个问题。让我们用根节点的子节点来遍历循环的下一次迭代。

for(int i = 0; i < childrenSize; i++){  // i = 1 and childrenSize of the root node is 2, so we can continue
    Node child = nodeChildrenList.get(i); // Uh oh, we don't have a child at index 1!

这里的问题是,由于对 nodeChildrenList 进行了结构修改,现在列表中只有一个元素(在索引 0 处) - 值为 3 的元素。在索引 1 处没有元素,所以这会引发 IndexOutOfBoundsException .这就是为什么在对正在循环的集合进行结构修改时要使用迭代器的原因。

【讨论】:

  • 真的谢谢,伙计。如果我从一开始就删除子节点,那么其他节点将被转移,如果我从末尾迭代列表,这也将得到解决。是的 child.children.remove 确实是一个错误。
  • 是的,您绝对可以向后工作并让这个案例工作。只需确保在调用node.children.remove 后移动removeLeaves,否则最终只剩下根节点。
猜你喜欢
  • 1970-01-01
  • 2011-11-24
  • 1970-01-01
  • 2016-07-08
  • 2017-08-02
  • 2015-08-25
  • 2013-12-24
  • 2013-12-14
  • 2015-12-22
相关资源
最近更新 更多