C# 二叉树中的节点交换

C# 二叉树中的节点交换,c#,binary-tree,C#,Binary Tree,我正试图在C#中编写一个函数,它允许我交换二叉树的两个节点,但它不能按预期工作 以下是使用交换方法的类: class Node { public int value; public Node parent { get; set; } public Node left { get; set; } public Node right { get; set; } public Node addLeft(int value) { this

我正试图在C#中编写一个函数,它允许我交换二叉树的两个节点,但它不能按预期工作

以下是使用交换方法的

class Node
{
    public int value;
    public Node parent { get; set; }
    public Node left { get; set; }
    public Node right { get; set; }

    public Node addLeft(int value)
    {
        this.left = new Node(value);
        this.left.parent = this;
        this.left.left = null;
        this.left.right = null;
        return this.left;
    }

    public Node addRight(int value)
    {
        this.right = new Node(value);
        this.right.parent = this;
        this.right.left = null;
        this.right.right = null;
        return this.right;
    }

    public Node(int value)
    {
        this.value = value;
        this.parent = null;
        this.left = null;
        this.right = null;
    }

    public Node getRoot()
    {
        Node n = this;
        while(n.parent!=null)
        {
            n = n.parent;
        }
        return n;
    }

    public static void swap(ref Node n1,ref Node n2)
    {
        //updating references of n1 and n2 parents
        if(n1.Equals(n1.parent.left)) //n1 is a left child
        {
            n1.parent.left = n2;
        }
        else if(n1.Equals(n1.parent.right)) //n1 is a right child
        {
            n1.parent.right = n2;
        }
        else
        {
            throw new Exception("Something is wrong");
        }
        if (n2.Equals(n2.parent.left)) //n2 is a left child
        {
            n2.parent.left = n1;
        }
        else if (n2.Equals(n2.parent.right)) //n2 is a right child
        {
            n2.parent.right = n1;
        }
        else
        {
            throw new Exception("Something is wrong");
        }
        //updating references of n1 and n2 childs
        if(n1.left != null)
        {
            n1.left.parent = n2;
        }
        if (n1.right != null)
        {
            n1.right.parent = n2;
        }
        if (n2.left != null)
        {
            n2.left.parent = n1;
        }
        if (n2.right != null)
        {
            n2.right.parent = n1;
        }
        //Swapping n1 and n2 references
        Node t_p = n1.parent;
        Node t_l = n1.left;
        Node t_r = n1.right;
        n1.parent = n2.parent;
        n1.left = n2.left;
        n1.right = n2.right;
        n2.parent = t_p;
        n2.left = t_l;
        n2.right = t_r;

    }
}

这是我的主要功能:

    static void Main(string[] args)
    {
        Node root = new Node(10);
        Node a = root.addLeft(1);
        Node b = root.addRight(2);
        Node c = a.addLeft(3);
        Node d = a.addRight(4);
        Node e = b.addLeft(5);
        Node f = b.addRight(6);
        Node g = d.addLeft(7);
        Node h = d.addRight(8);
        Node.swap(ref a,ref d);
        Console.WriteLine("Value is: " + root.left.value);
        Console.WriteLine("Value is: " + root.left.right.value);
        Console.WriteLine("Root: " + a.getRoot().value);
        Console.WriteLine("Root: " + d.getRoot().value);
        Console.Read();
    }

上述代码的输出为:

Value is: 4
Value is: 1
它挂在第二个控制台后。WriteLine和我不明白为什么。你能告诉我我做错了什么吗


编辑:

如果我多次尝试交换节点,就会抛出异常“有问题”

while (n.parent != null)
这个条件永远不会满足,所以你会陷入一个很长的循环中

swap方法创建一个具有无限祖先树(父)的节点。如果在
.getRoot()
中沿着当前节点
n
前进,则永远不会找到空的父节点

这是开始交换前树的状态

             ((Root(10))
            /           \
          a(1)          b(2)
         /    \        /   \
      c(3)    d(4)   e(5)  f(6)
             /    \
           g(7)   h(8)
如果仅将子节点交换为a&d,则最终会得到父节点的循环引用

对于您的交换方法,类似这样的方法应该可以工作。为了清楚起见,我留下了这篇冗长的文章

  public static void swap(ref Node A, ref Node B)
    {
        var newA = new Node(B.value);
        var newB = new Node(A.value);

        newA.left = A.left;
        newA.right = A.right;
        newA.parent = A.parent;

        newB.left = B.left;
        newB.right = B.right;
        newB.parent = B.parent;

        // Fix up parent node for A
        if (A.parent.left == A)
        {
            // A is a left node
            A.parent.left = newA;
        }
        if (A.parent.right == A)
        {
            // A is a Right node
            A.parent.right = newA;
        }

        // Fix up parent node for B
        if (B.parent.left == B)
        {
            // B is a left node
            B.parent.left = newB;
        }
        if (B.parent.right == B)
        {
            // B is a right node
            B.parent.right = newB;
        }


        if (newA.right == B)
        {
            // If B was a right child of A, update reference to newB
            newA.right = newB;
        }
        if (newA.left == A)
        {
            // If B was a left child of A, update reference to newB
            newA.left = newB;
        }

        if (newB.right == A)
        {
            // If A was a right child of B, update reference to newA
            newB.right = newA;
        }
        if (newB.left == A)
        {
            // If A was a left child of B, update reference to newA
            newA.left = newB;
        }

        // Update child references to be orphaned to point to new parents for A
        A.left.parent = newA;
        A.right.parent = newA;

        // Update child references to be orphaned to point to new parents for A
        B.left.parent = newB;
        B.right.parent = newB;

        // Final Swap to update ref types
        A = newA;
        B = newB;

    }
交换后的所需状态

         ((Root(10))
        /           \
      d(4)          b(2)
     /    \        /   \
  c(3)    a(1)   e(5)  f(6)
         /    \
       g(7)   h(8)
下面是一些在控制台中运行的快速脏验证代码。我还没有检查所有可能的情况,但它现在似乎更新了本例中的所有相关节点

    static void Main(string[] args)
    {
        var root = new Node(10);
        var a = root.addLeft(1);
        var b = root.addRight(2);
        var c = a.addLeft(3);
        var d = a.addRight(4);
        var e = b.addLeft(5);
        var f = b.addRight(6);
        var g = d.addLeft(7);
        var h = d.addRight(8);
        Node.swap(ref a, ref d);

        if (root.left.value != 4) 
            throw new ApplicationException("New Root --> left --> value != 4 as expected");
        Console.WriteLine("New root --> left node has correct value of 4");

        if ((root.left.right.parent != root.left))
            throw new Exception("and root --> left --> right has incorrect parent");   
        Console.WriteLine("Root --> left --> right has the correct parent"); 

        if (root.left.right.value != 1)
            throw new ApplicationException("New Root --> left --> right --> value did not equal 1.");
        Console.WriteLine("New Root --> Left --> right has the correct value of 1");

        if (root.left.right.left.value != 7)
            throw new ApplicationException("New Root --> left --> right --> left --> value was not 7 as expected.");
        Console.WriteLine("New Root --> left --> right --> left.value had a value of 7 as expected");

        if (root.left.right.left.parent != root.left.right)
            throw new ApplicationException("New Root --> left --> right --> left --> parent was not root --> left --> right as expected");
        Console.WriteLine("New Root --> Left --> right --> left has the correct value of 7 and expected parent");


        Console.Read();
    }

如果我理解您的交换函数应该做什么,它应该简单如下:

    public static void swap(Node n1, Node n2)
    {
        Node left1 = n1.left;
        Node left2 = n2.left;
        Node right1 = n1.right;
        Node right2 = n2.right;
        Node parent1 = n1.parent;
        Node parent2 = n2.parent;

        n1.left = left2;
        n2.left = left1;

        n1.right = right2;
        n2.right = right1;

        n1.parent = parent2;
        n2.parent = parent1;
    }

我认为你需要做的是简单地交换n1的左右和n2的左右。也不需要
ref
,因为它是引用类型而不是值类型。

请看这一行:

if (n1.right != null)
{
    n1.right.parent = n2;
}
如果n2是n1的右子级,如本例所示,
n1.right
n2
,因此
n1.right.parent=n2
实际上是导致循环的
n2.parent=n2


要解决此问题,您必须复制节点并替换它们,或者尝试以原子方式独立地进行这两种交换。

为什么要将带有
ref
的节点传递给
swap
?它可能挂起,只是因为getRoot没有返回,这意味着您可能会把更改父节点的操作弄糟。我的建议,在分配任何内容之前,请先提取所需的所有信息,然后将所有内容分配给它应该是的内容。您遇到了这样一个问题:更改某些值,然后在确实需要旧值时将更改的值分配给其他值。如果你把所有东西都安排得井井有条,你实际上可以不储存那么多东西就离开,但是如果你没有完全正确地安排好,你就会遇到这样的错误。@lared当两个代码都不是另一个的父代码时,代码工作得很好。这就是他的错误的原因。这是不是一个实际的要求,即不创建新的节点来进行交换?此外,节点是否必须交换?就我所知,如果没有使用子树,您就不能交换值而不是整个节点吗?这只会导致一个问题,为什么父节点没有设置为正确的值,从而导致循环。这并不能回答这个问题。或者至少,答案是如此模糊,以至于完全没有帮助。如果你说“因为你有一个bug”,那么从技术上讲你是对的,同样没有帮助。@ChrisBallance:你还没有真正回答这个问题。您刚刚重述了症状。您最近的编辑仍然是错误的。它不会更新所有子节点的父引用或父节点的子引用。仅供参考:如果与叶节点交换,则会失败。例如,
swap(d,e)
父节点也需要交换。也可以通过简单地交换值来完成,但在这种情况下,必须确保代码中其他地方没有对树节点的引用。