C#节点指针问题

C# Node pointer issue

我在使用 C# 设置子节点时遇到了一些问题。我正在尝试构建一个节点树,其中每个节点都包含一个 int 值,并且最多可以有多个等于它的值的子节点。

当我在一个节点中迭代寻找空(null)子节点以便我可以在该位置添加一个新节点时,我的问题出现了。我可以找到并 return 空节点,但是当我为它设置新节点时,它会失去与父节点的连接。

因此,如果我添加 1 个节点,那么它会链接到我的头节点,但如果我尝试添加第二个,它不会成为头节点的子节点。我正在尝试使用单元测试来构建它,所以这里是测试代码,显示头部确实不显示新节点,因为它是子节点(也通过 visual studios 调试器确认):

  [TestMethod]
  public void addSecondNodeAsFirstChildToHead()
  {
     //arange
     Problem3 p3 = new Problem3();
     p3.addNode(2, p3._head);
     Node expected = null;
     Node expected2 = p3._head.children[0];
     int count = 2;

     //act
     Node actual = p3.addNode(1, p3._head);
     Node expected3 = p3._head.children[0];

     //assert
     Assert.AreNotEqual(expected, actual, "Node not added"); //pass
     Assert.AreNotEqual(expected2, actual, "Node not added as first child"); //pass
     Assert.AreEqual(expected3, actual, "Node not added as first child"); //FAILS HERE
     Assert.AreEqual(count, p3.nodeCount, "Not added"); //pass
  }

这是我的代码。

public class Node
   {
      public Node[] children;
      public int data;

      public Node(int value)
      {
         data = value;
         children = new Node[value];

         for(int i = 0; i < value; i++)
         {
            children[i] = null;
         }
      }
   }

   public class Problem3
   {
      public Node _head;
      public int nodeCount;

      public Problem3()
      {
         _head = null;
         nodeCount = 0;
      }

      public Node addNode(int value, Node currentNode)
      {
         if(value < 1)
         {
            return null;
         }

         Node temp = new Node(value);

         //check head
         if (_head == null)
         {
            _head = temp;
            nodeCount++;
            return _head;
         }

         //start at Current Node
         if (currentNode == null)
         {
            currentNode = temp;
            nodeCount++;
            return currentNode;
         }

         //find first empty child
         Node emptyChild = findEmptyChild(currentNode);
         emptyChild = temp;
         nodeCount++;
         return emptyChild;
      }

      public Node findEmptyChild(Node currentNode)
      {
         Node emptyChild = null;
         //find first empty child of current node
         for (int i = 0; i < currentNode.children.Length; i++)
         {
            if (currentNode.children[i] == null)
            {
               return currentNode.children[i];
            }
         }
         //move to first child and check it's children for an empty
         //**this causes values to always accumulate on left side of the tree
         emptyChild = findEmptyChild(currentNode.children[0]);
         return emptyChild;
      }

我觉得问题在于我正在尝试像在 C++ 中那样将节点视为指针,但它并没有像我预期的那样工作。

函数不可能 return 指向尚不存在的对象的句柄(或指针)。要么在函数内部初始化不存在的值,要么提供足够的变量使其在函数外部初始化。

一个解决方案是将函数 findEmptyChild 重命名为 initializeEmptyChild(Node currentNode, Node newNode) 之类的名称,再向其添加一个 Node 参数(调用时为 temp值),并在 return 之前的循环中初始化之前为空的 NodecurrentNode.children[i] = newNode.

另一种解决方案不是 return 只有一个 Node 而是两个值,一个父节点和一个找到空子节点的索引 Tuple<Node, int> findEmptyChild(Node currentNode),而是在循环中的 return currentNode.children[i] 你做了 return new Tuple<Node, int>(currentNode, i)。调用该函数时,您会将代码更改为

var parentAndIndex = findEmptyChild(currentNode);
parentAndIndex.Item1.children[parentAndIndex.Item2] = temp;

查看您的这部分代码:

Node temp = new Node(value);
//...
Node emptyChild = findEmptyChild(currentNode);
emptyChild = temp;

您正在将 emptyChild 分配给一个新节点,这样做您将 "loose" 与任何父节点建立连接。你应该这样写:

emptyChild.data = temp.data;
emptyChild.children = temp.children;

正如其他人所说,您使用 null 检查的方法可以改进。您提到 Node.data 包含给定节点的子节点数,因此您可以简单地说,当您有 Node.data == 0 时,该节点应被视为 null 或空。例如,而不是:

rootNode.children[0] = null; // rootNode can have a lot of children
rootNode.children[1] = null;
//...

你会:

rootNode.children[0] = new Node(0);
rootNode.children[1] = new Node(0);
//...

此时您的代码将类似于:

public class Node
{
    public Node[] children;
    public int data;

    public Node(int value)
    {
        data = value;
        children = new Node[value];

        // Instead of "pointing" to null,
        // create a new empty node for each child.
        for (int i = 0; i < value; i++)
        {
            children[i] = new Node(0);
        }
    }
}

public class Problem3
{
    public Node _head;
    public int nodeCount;

    public Problem3()
    {
        _head = null;
        nodeCount = 0;
    }

    public Node addNode(int value, Node currentNode)
    {
        if (value < 1)
        {
            return null;
        }

        Node temp = new Node(value);

        //check head
        if (_head == null)
        {
            _head = temp;
            nodeCount++;
            return _head;
        }

        //start at Current Node
        if (currentNode == null)
        {
            currentNode = temp;
            nodeCount++;
            return currentNode;
        }

        //find first empty child
        Node emptyChild = findEmptyChild(currentNode);
        if (emptyChild != null)
        {
            emptyChild.data = temp.data;
            emptyChild.children = temp.children;
            nodeCount++;
        }
        return emptyChild;
    }

    public Node findEmptyChild(Node currentNode)
    {
        // Null checking.
        if (currentNode == null)
            return null;

        // If current node is empty, return it.
        if (currentNode.data == 0)
            return currentNode;

        // If current node is non-empty, check its children.
        // If no child is empty, null will be returned.
        // You could change this method to check even the
        // children of the children and so on...
        return currentNode.children.FirstOrDefault(node => node.data == 0);
    }
}

现在让我们看一下测试部分(请看评论说明):

[TestMethod]
public void addSecondNodeAsFirstChildToHead()
{
    //arange
    Problem3 p3 = new Problem3();
    p3.addNode(2, p3._head); // Adding two empty nodes to _head, this means that now _head can
                             // contain two nodes, but for now they are empty (think of them as
                             // being "null", even if it's not true)
    Node expected = null;
    Node expected2 = p3._head.children[0]; // Should be the first of the empty nodes added before.
                                           // Be careful: if you later change p3._head.children[0]
                                           // values, expected2 will change too, because they are
                                           // now pointing to the same object in memory
    int count = 2;

    //act
    Node actual = p3.addNode(1, p3._head); // Now we add a non-empty node to _head, this means
                                           // that we will have a total of two non-empty nodes:
                                           // this one fresly added and _head (added before)
    Node expected3 = p3._head.children[0]; // This was an empty node, but now should be non-empty
                                           // because of the statement above. Now expected2 should
                                           // be non-empty too.

    //assert
    Assert.AreNotEqual(expected, actual, "Node not added"); //pass

    // This assert won't work anymore, because expected2, expected 3 and actual 
    // are now pointing at the same object in memory: p3._head.children[0].
    // In your code, this assert was working because 
    // In order to make it work, you should replace this statement:
    //    Node expected2 = p3._head.children[0];
    // with this one:
    //    Node expected2 = new Node(0); // Create an empty node.
    //    expected2.data = p3._head.children[0].data; // Copy data
    //    expected2.children = p3._head.children[0].children;
    // This will make a copy of the node instead of changing its reference.
    Assert.AreNotEqual(expected2, actual, "Node not added as first child");

    // Now this will work.
    Assert.AreEqual(expected3, actual, "Node not added as first child");
    Assert.AreEqual(count, p3.nodeCount, "Not added"); //pass
}