【问题标题】:Add nodes in a binary search tree在二叉搜索树中添加节点
【发布时间】:2016-02-20 06:32:27
【问题描述】:

注意:这是一项家庭作业,我只希望得到指导。那我不要求任何其他帮助。

我查看了如何在 C# 中的二叉搜索树中添加节点,但我得到了非常不同类型的答案。所以,假设我有节点类:

class Node
{
    public int value;
    public Node left;
    public Node right;

    public Node(int v)
    {
        value = v;
        left = right = null;
    }
}

而我的 add 函数所在的类是:

class BST
{
    public void addNode(int x, Node root)
    {
        Node newNode = new Node(x);

        if (root == null)
        {
            root = newNode;
        }
        if (root.value == newNode.value)
        {
            return;
        }
        else if (newNode.value < root.value)
        {
            if (root.left == null)
            {
                root.left = newNode;
            }
            else
            {
                addNode(x, root.left);
            }
        }
        else if (newNode.value > root.value)
        {
            if (root.right == null)
            {
                root.right = newNode;
            }
            else
            {
                addNode(x, root.right);
            }
        }
    }
}

所以我现在的问题是:我很确定我的大部分逻辑都是正确的,但我怀疑的是,当我递归调用函数时,我不确定树是否真的要使用左子树那个递归调用。还是我做对了?

【问题讨论】:

  • 我认为现在是了解breakpoints 的好时机,通过这些,您可以逐步检查每一行代码,以确保它正在执行您认为正在执行的操作。 (顺便说一句:如果它不等于且不小于,那么它必须大于所以你可以使用else而不是else if
  • 您可能会对单元测试感兴趣(例如NUnitVS-UnitTest)。有了这个,您可以检查是否所有星座都得到了适当的处理。在这种情况下,您知道您的代码有效。
  • 我会查一下 Sayse,谢谢你的提示!

标签: c# binary-search-tree


【解决方案1】:

如果你认为你做对了,你应该测试一下。首先,您应该增强您的类,使它们更容易在调试器中可视化。显而易见的做法是在 Node 中添加一个“ToString()”方法:

public class Node
{
    public int value;
    public Node left;
    public Node right;

    public Node(int v)
    {
        value = v;
        left = right = null;
    }

    public override string ToString()
    {
        return value.ToString();
    }
}

现在,您只需将鼠标悬停在类实例上即可在 Visual Studio 中检查树的值。按照 Sayse 的建议,在开始构建树后在代码中设置断点:

将鼠标悬停在根节点上。将出现一个带有“ToString()”值的面板——节点值,事实上,这要归功于您的“ToString()”覆盖。您还将看到一个“+”按钮。如果单击该按钮,Visual Studio 将向您显示类实例的字段和属性,您可以递归地展开它们:

检查了调试器中的值后,您现在可能需要更复杂的方法从类中提取数据,例如返回列表中Node 中或下方的所有值:

public class Node
{
    public IList<int> ToList()
    {
        var list = new List<int>();
        AddToList(list);
        return list;
    }

    public void AddToList(List<int> list)
    {
        if (left != null)
            left.AddToList(list);
        list.Add(value);
        if (right != null)
            right.AddToList(list);
    }
}

这应该可以让您看到您所拥有的并验证它是否符合您的期望。您可以使用Enumerable.SequentialEqual 在代码中添加asserts 以确保返回的列表正确,如下所示:

    private static void TestBST()
    {
        var bst = new BST();
        Node root = new Node(23);
        bst.addNode(13, root);
        bst.addNode(-12, root);
        bst.addNode(1, root);
        Debug.Assert(Enumerable.SequenceEqual(root.ToList(), new int[] { -12, 1, 13, 23 }));
    }

您还可以从Immediate Window 调用“ToList()”方法,该方法允许您在程序在断点处停止时键入 c# 表达式并解释它们并以交互方式显示结果:

因此,总而言之,每当您设计新类时,请始终添加逻辑以方便可视化、调试和断言代码的正确性。 TobiMcNamobi 提到的单元测试方法是执行此操作的正式方法,但您可以在作业代码中非正式地执行此操作。

(顺便说一句,你注意到我在上面明确分配了一个根节点,而不是使用 BST 类实例来创建根节点?你剩下的功课是找出原因。)

【讨论】:

    【解决方案2】:

    您的左/右逻辑在大多数情况下是可以的,但开头令人困惑:

    if (root == null)
    {
       root = newNode;
    }
    if (root.Value == newNode.Value) 
    {
        return;
    } 
    

    实际上太多了。如果 rootnull 则有问题。 你可能会忽略:

    if (root == null) 
        return;    
    
    if (newNode.Value < root.Value)
    

    或者现在让用户知道有什么问题

    if (root == null) 
        throw new ArgumentNullException("root");
    

    但是,如果您希望在AddNode调用之外更改root,您必须将root 作为reference 传递:

     public void AddNode(int x, ref Node root) 
     {
         Node newNode = new Node(x);
         this->AddNode(newNode, ref root);
     }
    
     private void AddNode(Node newNode, ref Node root)
     {
          if (root == null) 
          {
               root = newNode;
               return;
          }
          if (root.Value == newNode.Value) 
          {
               return;
          } 
    

    一些需要改进的地方:

    最好在 C# 中以 UpperCamelCase 形式编写方法名称,因此您可以将方法称为 AddNode。这不会改变逻辑,但每种编程语言都有自己的风格指南,应该尝试遵循。 公共属性/字段也是如此,所以称它们为LeftRightValue

    每次调用 AddNode 时,都会创建一个新的 Node 对象来保存 x。 这是可以避免的开销。只需创建另一个函数AddNode,它接收一个节点作为第一个参数并从您的函数中调用该函数:

    public void AddNode(int x, Node root) 
    {
        Node newNode = new Node(x);
        this->AddNode(newNode, root);
    }
    
    private void AddNode(Node newNode, Node root)
    {
        if (root == null) 
        {
            root = newNode;
        }
        if (root.Value == newNode.Value) 
        {
            return;
        } 
        else if (newNode.Value < root.Value) 
        {
            if (root.Left == null) 
            {
                root.Left = newNode;
            } 
            else 
            {
                AddNode(newNode, root.Left);
            }
        } 
        else if (newNode.Value > root.Value) 
        {
            if (root.Right == null) 
            {
                root.Right = newNode;
            } 
            else 
            {
                AddNode(newNode, root.Right);
            }
        }
    }
    

    【讨论】:

    • hmm.. 但问题是当 root == null 时,这意味着树有一个根值,我将新节点作为根。你不能那样做吗?
    • 不,不是这样,因为设置root = newNode 只会在方法内部设置root。我会更改方法签名,所以它会做你想要的。
    猜你喜欢
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多