对象没有从ArrayList中删除

问题描述

一个通用的树数据结构。现在,它的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
    }
}
}

我无法理解object(Node)子级没有从ArrayList中删除。但是Iterator工作正常。我什至在每一步都尝试调试。当断点到达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);
    //     }
    // }
  }

解决方法

让我们看一下带有for循环的removeLeaves在做什么。在下面添加了评论

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 。这就是为什么要对要遍历的集合进行结构修改时要使用迭代器的原因。