在C#中实现完整的二叉树,而不是二进制搜索树

时间:2016-10-21 20:52:15

标签: c# algorithm tree binary-tree

我正在尝试在C#中实现二叉树,而不是二进制搜索树。我实现了下面的代码,它工作正常,但不是我想要的。基本上我正在尝试实现一个完整的二叉树,但是使用我的下面的代码,我得到一个不平衡的二叉树。

Input : 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
Desired Output : 

                        10
                     /       \
                 20            30
               /    \         /  \
            40        50    60    70
           /  \      /
         80    90  100     


Current Output : 
                                10
                              /    \
                            20      30
                                  /    \
                                40      50    
                                       /   \
                                     60     70
                                           /  \
                                         80    90  
                                              /
                                            100   

这是我的代码:

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

    public Node() 
    {
      data = 0;
      left = null;
      right = null;
    }
  }

  class Tree 
  {
    private Node root;

    public Tree() 
    {
      root = null;
    }

    public void AddNode(int data)
    {
      root = AddNode(root, data);
    }

    public Node AddNode(Node node, int data) 
    {
      if (node == null)
      {
        node = new Node();
        node.data = data;
      }
      else
      {
        if (node.left == null)
        {
          node.left = AddNode(node.left, data);
        }
        else
        {
          node.right = AddNode(node.right, data);
        }
      }
      return node;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      int[] nodeData = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
      Tree tree1 = new Tree();
      foreach (int i in nodeData)
      {
        tree1.AddNode(i);
      }
      Console.ReadKey();
    }
  }

我知道问题出在我的AddNode(Node node,int data){...}函数的else块中,但我无法弄清楚解决方案。

我试图在线寻找解决方案,但大多数地方的二进制搜索树实施。我喜欢的解决方案之一是here,但解决方案是将输入数组作为递归调用的参数传递,我不知道在非常大的树的情况下是否有效。还有其他几个帖子,但没有一个是解决我的问题。

虽然我在C#中实现它,但更具体地说,我正在寻找修复我的AddNode(...)函数的逻辑,所以如果不是代码实现,我对算法很好。

任何帮助?

9 个答案:

答案 0 :(得分:3)

您需要按级别填充树级别。级别n具有2^n个节点,即根目录中有2^n个路径。每个路径都可以编码为n - 位数(0表示左侧分支,1表示右侧。也就是说,要填充n级别,

    for path from 0 to 2^n - 1
        value = get_next_value()
        node = root
        for level = 0 to n - 1
            if path & 0x1 == 0
                node = node->left
            else
                node = node->right
            ++level
            path >>= 1
        if path == 0
            node->left = new node(value)
        else
            node->right = new node(value)

答案 1 :(得分:3)

树根据定义是递归数据结构。

class Node<T>
{
    public Node(T data)
    {
        Data = data;
    }
    public T Data { get; }
    public Node<T> Left { get; set; }
    public Node<T> Right { get; set; }
}

因此,使用递归构造它们更加直观。

Input: 10, 20, 30, 40, 50, 60, 70, 80, 90, 100

Desired output --complete binary tree:

               10
           /        \
         20          30
      /     \     /      \
    40       50  60      70
  /   \     /    
80     90  100

Matching index of input:

               0
           /       \
          1         2
      /     \     /     \
    3        4   5       6
  /   \     /    
 7     8   9

对于索引为i的节点,出现了一种模式:

  • 左边的孩子有索引2 * i + 1
  • 正确的孩子有索引2 * i + 2

使用递归的基本情况,

i >= input.length

我们需要做的就是在root上调用递归方法。

class TreeBuilder<T>
{
    public Node<T> Root { get; }

    public TreeBuilder(params T[] data)
    {
        Root = buildTree(data, 0);
    }

    private Node<T> buildTree(T[] data, int i)
    {
        if (i >= data.Length) return null;
        Node<T> next = new Node<T>(data[i]);
        next.Left = buildTree(data, 2 * i + 1);
        next.Right = buildTree(data, 2 * i + 2);
        return next;
    }
}

用法:

int[] data = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
TreeBuilder<int> builder = new TreeBuilder<int>(data);
Node<int> tree = builder.Root;

切换API,有两种方法可以逐个添加节点:

  • 从根向下遍历树(绝对)
  • 从之前添加的节点(相对)
  • 旅行
  • 维护没有两个子节点的有序节点集合

由于第二个涉及较长的行程距离(树的高度为2 *),而第三个已经实施(用于记账的队列),让我们看看第一个。

这一次,可视化给定位置的节点数:

               1
           /        \
         2           3
      /     \     /     \
    4        5   6       7 
  /   \     /    
8      9   10 

映射到二进制表示:

               1
           /        \
         10          11
      /     \     /     \
   100     101  110     111 
  /   \     /    
1000  1001 1010 

如果我们再次忽略最左边的位,则会出现一个模式。我们可以将这些位用作路线图,或者在这种情况下使用节点图。

class TreeBuilder<T>
{
    private int level;
    private int nodeCount;
    public Node<T> Root { get; }

    public TreeBuilder(T data)
    {
        Root = new Node<T>(data);
        nodeCount = 1;
        level = 0;
    }

    public void addNode(T data)
    {
        nodeCount++;
        Node<T> current = Root;
        if (nodeCount >= Math.Pow(2, level + 1)) level++;
        for (int n=level - 1; n>0; n--)
            current = checkBit(nodeCount, n) ? current.Left : current.Right;
        if (checkBit(nodeCount, 0))
            current.Left = new Node<T>(data);
        else
            current.Right = new Node<T>(data);
    }

    private bool checkBit(int num, int position)
    {
        return ((num >> position) & 1) == 0;
    }
}

用法:

int[] data = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
TreeBuilder<int> builder = new TreeBuilder<int>(data[0]);
for (int i=1; i<data.Length; i++)
{
    builder.addNode(data[i]);
}
Node<int> tree = builder.Root;

答案 2 :(得分:2)

该算法可以非常简单有效地解决您的问题。

考虑这个Node类:

public class Node<T>
{
     public Node(T data) 
     {
         Data = data;
     }

    public T Data { get; }

    public Node<T>  Left { get; set;}

    public Node<T>  Right { get; set;}
}

这个课程将帮助你构建你的树:

public class TreeBuilder<T>
{
    private readonly Queue<Node<T>> _previousNodes;

    public Node<T> Root { get; }

    public TreeBuilder(T rootData)
    {
        Root = new Node<T>(rootData)
        _previousNodes = new Queue<Node<T>>();
        _previousNodes.Enqueue(Root);
    }

    public void AddNode(T data)
    {
        var newNode = new Node<T>(data);
        var nodeToAddChildTo = _previousNodes.Peek();
        if(nodeToAddChildTo.Left == null)
        {
           nodeToAddChildTo.Left = node; 
        }
        else
        {
            nodeToAddChildTo.Right = node;
            _previousNodes.Dequeue();
        }      
        _previousNodes.Enqueue(newNode);
    } 
}

AddNode方法背后的逻辑基于FIFO方法,因此我们将在实现中使用Queue<T>

我们将从第一个节点开始,然后首先向它附加一个左子节点(然后将其添加到队列中),然后我们将附加一个右节点(并且之后还将其添加到队列中)并且仅在之后我们将附加两个我们将其从队列中删除并开始将子项附加到它的左子(这是队列中的下一个),当我们完成它时,我们将开始将子项附加到它的正确的孩子(这将是在队列中的下一个),我们将从上到下不断地从左到右进行此操作,直到树组成。

现在您可以在主方法中使用它,如下所示:

public class Program
{
    public static void Main(string[] args)
    {
        int[] values = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
        var treeBuilder = new TreeBuilder<int>(values[0]);
        foreach (int value in values.Skip(1))
        {
            treeBuilder.AddNode(value);
        }
        //here you can use treeBuilder Root property as an entry point to the tree
    }
}

答案 3 :(得分:1)

这是另一种无需跟踪左,右和/或父母即可获得答案的方法。实际上Node类变得非常简单,Tree类在你调用tree1.Root ...时工作。我在下面使用构造函数来添加值,但是我已经包含了AddNodes方法,你可以在其中添加一个新节点作为您希望通过一次调用添加许多值。

  class Node<T>
  {
    public T Data { get; set; }
    public Node<T> Left { get; set; }
    public Node<T> Right { get; set; }

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


  class Tree<T>
  {
    private readonly List<T> list;

    private static readonly Func<List<T>, int, Node<T>> LeftFunc = (l, i) =>
    {
      var lix = Convert.ToInt32(Convert.ToString(i, 2) + "0", 2) - 1;
      return l.Count > lix ? new Node<T> {Data = l[lix], Left = LeftFunc(l, lix + 1), Right = RightFunc(l, lix + 1) } : null;
    };

    private static readonly Func<List<T>, int, Node<T>> RightFunc = (l, i) =>
    {
      var rix = Convert.ToInt32(Convert.ToString(i, 2) + "1", 2) - 1;
      return l.Count > rix ? new Node<T> { Data = l[rix], Left = LeftFunc(l, rix + 1), Right = RightFunc(l, rix + 1) } : null;
    };

    public Node<T> Root => list.Any() ? new Node<T>{Data=list.First(), Left = LeftFunc(list,1), Right= RightFunc(list,1)} : null;

    public Tree(params T[] data)
    {
      list = new List<T>(data);
    }

    public int Count => list.Count;

    public void AddNodes(params T[] data)
    {
      list.AddRange(data);
    }


    public double Levels => Math.Floor(Math.Log(list.Count,2))+1;

    public override string ToString()
    {
      return  (list?.Count ?? 0).ToString();
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      var nodeData = new [] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
      var tree1 = new Tree<int>(nodeData);



      Console.ReadKey();
    }

为了好玩,我创建了一个扩展,将树写入控制台,以帮助可视化树。为了使用它,您需要确保Node和Tree类是公共的,并将 Values 属性添加到Tree类中。所以你的Tree类看起来像这样:

  public class Tree<T>
  {
    private readonly List<T> list;

    private static readonly Func<List<T>, int, Node<T>> LeftFunc = (l, i) =>
    {
      var lix = Convert.ToInt32(Convert.ToString(i, 2) + "0", 2) - 1;
      return l.Count > lix ? new Node<T> {Data = l[lix], Left = LeftFunc(l, lix + 1), Right = RightFunc(l, lix + 1) } : null;
    };

    private static readonly Func<List<T>, int, Node<T>> RightFunc = (l, i) =>
    {
      var rix = Convert.ToInt32(Convert.ToString(i, 2) + "1", 2) - 1;
      return l.Count > rix ? new Node<T> { Data = l[rix], Left = LeftFunc(l, rix + 1), Right = RightFunc(l, rix + 1) } : null;
    };

    public Node<T> Root => list.Any() ? new Node<T>{Data=list.First(), Left = LeftFunc(list,1), Right= RightFunc(list,1)} : null;

    public Tree(params T[] data)
    {
      list = new List<T>(data);
    }

    public int Count => list.Count;

    public void AddNodes(params T[] data)
    {
      list.AddRange(data);
    }

    public IEnumerable<T> Values => list.ToArray();

    public double Levels => Math.Floor(Math.Log(list.Count,2))+1;

    public override string ToString()
    {
      return  (list?.Count ?? 0).ToString();
    }
  }

这是扩展类:

  public static class TreeAndNodeExtensions
  {
    public static void Write<T>(this Tree<T> tree)
    {
      var baseMaxNodes = Convert.ToInt32(Math.Pow(2, tree.Levels - 1));

      // determine the required node width based on the the last two levels and their value lengths...
      var nodeWidth = Math.Max(tree.Values.Skip(Convert.ToInt32(Math.Pow(2, tree.Levels - 2) - 1)).Max(n => n.ToString().Length), tree.Values.Skip(Convert.ToInt32(Math.Pow(2, tree.Levels - 1) - 1)).Max(n => n.ToString().Length) + 1) + 1;

      var baseWidth = baseMaxNodes * nodeWidth;
      Console.CursorLeft = baseWidth/2;
      tree.Root.Write(baseWidth);
    }

    private static void Write<T>(this Node<T> node, int nodeWidth, int level=0)
    {
      var cl = Console.CursorLeft;
      var ct = Console.CursorTop;

      if (Console.CursorLeft >= Convert.ToInt32(Math.Ceiling(node.Data.ToString().Length / 2.0)))
      {
        Console.CursorLeft -= Convert.ToInt32(Math.Ceiling(node.Data.ToString().Length / 2.0));
      }
      Console.Write(node.Data);
      if (node.Left != null)
      {
        var numCenter = cl - nodeWidth/4;
        Console.CursorLeft = numCenter;
        Console.CursorTop = ct + 2;
        Console.Write('/');
        Console.CursorTop = ct + 1;
        Console.Write(new string('_',cl-Console.CursorLeft));
        Console.CursorLeft = numCenter;
        Console.CursorTop = ct+3;
        node.Left.Write(nodeWidth/2, level+1);
      }

      if (node.Right != null)
      {
        var numCenter = cl + nodeWidth/4;
        Console.CursorLeft = cl;
        Console.CursorTop = ct + 1;
        Console.Write(new string('_', numCenter-cl-1));
        Console.CursorTop = ct + 2;
        Console.Write('\\');
        Console.CursorLeft = numCenter;
        Console.CursorTop = ct+3;
        node.Right.Write(nodeWidth/2,level + 1);
      }

      Console.SetCursorPosition(cl,ct);
    }
  }

然后您可以更新您的程序以使用此扩展程序:

  class Program
  {
    static void Main(string[] args)
    {
      var nodeData = new [] { 10, 20, 30, 40, 50, 60, 70, 80,90,100 };
      var tree1 = new Tree<int>(nodeData);

      tree1.Write();

      Console.ReadKey();
    }
  }

你应该看到这个:

                   10
           __________________
          /                  \
         20                  30
      ________            ________
     /        \          /        \
    40        50        60        70
    __        _
   /  \      /
  80  90   100

答案 4 :(得分:0)

二叉树是一种非平衡结构。树的布局取决于插入值的顺序。您可以拥有两个具有完全相同值的树,但以不同的顺序插入,树将看起来完全不同。

对于平衡树,请查看AVL树。这是一种流行的自平衡实现。

但实际上,实际使用的是树木。字典更好,但如果你正在学习树,请查看AVL树:)。

答案 5 :(得分:0)

每个节点都需要知道它的父节点,根节点是异常,因为它将具有空父节点。然后每个节点将需要知道它最后传递一个值的路径。然后,当一个节点被要求添加一个子节点时,它将按以下顺序进行:left,right,parent,left,right,parent,...(根节点是例外,因为它将跳过父节点,并且只是左,右交替,左,右......)

我很快调整了您的代码,使其按预期工作,这可以在一点时间内更清晰。

 class Node
  {
    private readonly Node parent;
    private Direction lastPath;

    public int Data { get; set; }
    public Node Left { get; set; }
    public Node Right { get; set; }

    public Node(int data, Node parent = null)
    {
      Data = data;
      this.parent = parent;
    }

    public Node AddChild(int data)
    {
      if (Left == null)
      {
        lastPath = Direction.Left;
        Left = new Node(data, this);
        return this;
      }
      if (Right == null)
      {
        lastPath = Direction.Right;
        Right = new Node(data, this);
        return parent ?? this;
      }

      if (lastPath == Direction.Parent || parent==null && lastPath == Direction.Right)
      {
        lastPath = Direction.Left;
        return Left.AddChild(data);
      }

      if (lastPath == Direction.Left)
      {
        lastPath = Direction.Right;
        return Right.AddChild(data);
      }

      lastPath = Direction.Parent;
      return parent?.AddChild(data);
    }
  }

  enum Direction
  {
    Left,
    Right,
    Parent
  }

  class Tree
  {
    public Node Root { get; private set; }
    private Node current;

    public void AddNode(int data)
    {
      if (current == null)
      {
        Root = new Node(data);
        current = Root;
      }
      else
      {
        current = current.AddChild(data);
      }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      var nodeData = new [] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
      var tree1 = new Tree();

      foreach (var data in nodeData)
      {
        tree1.AddNode(data);
      }
      Console.ReadKey();
    }
  }

答案 6 :(得分:0)

回答您的问题:

传递数组是通过将函数传递给数组来完成的。所以在性能方面,它与传递任何类型的对象(https://msdn.microsoft.com/en-us/library/bb985948.aspx)完全相同。

Personnaly说,我不是总是传递相同对象的递归函数的忠实粉丝,也不是调用将调用构造函数的递归函数的构造函数。

没有数组,这是一个解决方案:

有一点需要注意的是,在这样的树中,如果从1开始寻址,节点地址可用于查找节点:

                0001
        0010            0011
    0100    0101    0110   0111
1000

因此,如果您跟踪树中的节点数,您就会知道要添加的下一个项目位于地址1001.

为了正确地做到这一点,我们可以找到父节点(1001右移一次:100)然后决定我们是向左还是向右添加(取决于1001模2 = 1:必须添加到右边)。

所以这是可以做到的代码:

节点类

//Having a generic here allows you to create a tree of any data type
class Node<T> 
{
    public T Data { get; set; }
    public Node<T> Left { get; set; }
    public Node<T> Right { get; set; }
    public Node(T Data)
    {
        this.Data = Data;
    }
}

树类

class Tree<T>
{
    Node<T> root = null;
    int nodeCount = 0;

    public void AddNode(T Data)
    {
        AddNode(new Node<T>(Data));
    }

    public void AddNode(Node<T> Node)
    {
        nodeCount++;
        if (root == null)
            root = Node;
        else
        {
            //First we find the parent node
            Node<T> parent = FindNodeWithAddress(nodeCount >> 1);
            //Then we add left or right
            if (nodeCount % 2 == 0)
                parent.Left = Node;
            else
                parent.Right = Node;
        }
    }

    private Node<T> FindNodeWithAddress(int address)
    {
        if (address == 1)
            return root;
        //To find the proper address we use the same trick
        //We first find our parent's address
        Node<T> parent = FindNodeWithAddress(address >> 1);
        //Then we go left or right
        return (address % 2 == 0 ? parent.Left : parent.Right);
    }
}

答案 7 :(得分:0)

您是否尝试使用数组实现它?

您将使用数组和一个int来保存最后使用的位置 对于任何位置pos,左侧“子节点”将位于2*pos位置 正确的“子节点”将位于2*pos+1的位置 并且“父节点”将位于pos/2

位置

(不要认为这段代码在语法上是正确的,这只是一个例子)

template<class T>
class CompleteBinTree<T>{
    private int[] arr;
    private int arrSize;
    private int pos;

    public CompleteBinTree(){
        arrSize = 100;
        arr = new int[arrSize]//you can always change this number
        int pos = 1; //you can use 0 instead of 1, but this way it's easier to understand
    }

    public AddNode(T t){
        if(pos + 1 == arrSize){
            int[] aux = int[arrSize];
            for(int i = 0; i < arrSize; i++)
                aux[i] = arr[i];
            arr = aux[i];
            arrSize = arrSize * 2;
        }
            arr[pos] = t;
            pos++;
    }

    public IndexOfLeftSon(int x){
        return 2*x;
    }

    public IndexOfRightSon(int x){
        return 2*x + 1;
    }

    public DeleteNode(int x){
        for(int i = x; i < pos; i++)
            arr[i] = arr[i+1];
    }
}

答案 8 :(得分:0)

鉴于你真的想构建一个已分配节点的树,而不是其他人建议的数组,有一个非常简单的算法:使用一个位置队列(给定节点的左或右子节点或根节点)从上到下扩展树。在尾部添加新位置并从头部移除以添加每个连续节点。没有递归。

抱歉,目前我无法访问C#环境,因此我将在Java中展示这一点。翻译应该非常简单。

ALTER PROCEDURE spIsUnique
    @columnname NVARCHAR(MAX),
    @tablename NVARCHAR(MAX)
AS
BEGIN
    EXEC ('select IIf (count(*)>1,''False'',''True'') as [IsUnique-check] 
           from '+@tablename+' 
           group by '+@columnname)
END

DECLARE @start INT, @count INT,
        @applicableforcolumn VARCHAR(MAX),
        @name VARCHAR(MAX)

SET @start = 1

SELECT @count = COUNT(*) 
FROM dbo.fnplatformnumber23()

WHILE @start <= @count
BEGIN
    SELECT @name = name 
    FROM sys.columns 
    WHERE object_id = OBJECT_ID('fnplatformnumber23') 
      AND column_id = @start

    IF @name = 'IsUnique'
    BEGIN
        SELECT @applicableforcolumn = ApplicableForColumn
        FROM dbo.fnplatformnumber23()
        WHERE IsUnique IS NOT NULL;

        EXEC spIsUnique @applicableforcolumn,'fnproduct()'
        --declare @alter nvarchar(max)= 'alter table ##producttable add [IsUnique-check] nvarchar(max);'
        --exec sp_executesql @alter

        UPDATE ##producttable 
        SET [IsUnique-check] = EXEC spIsUnique @applicableforcolumn 'fnproduct()' from OPENROWSET('SQL Server',
    'Server=DESKTOP-JEQ4NUR\SQLEXPRESS;Trusted_Connection=yes;',

    'exec spIsUnique '+@applicableforcolumn+' ''fnproduct()'';'
)
    END

    SET @start = @start + 1
END

在运行时,这会产生,如你所愿,

import java.util.ArrayDeque;
import java.util.Deque;

public class CompleteBinaryTree {
  final Deque<Location> queue = new ArrayDeque<>();

  /** Build a tree top-down in levels left-to-right with given node values. */
  Node build(int [] vals) {
    Node root = null;   
    queue.clear();
    queue.add(new Location(root, Location.Kind.ROOT));
    for (int val : vals) {
      Location next = queue.pollFirst();
      switch (next.kind) {
      case ROOT: root = addNode(val); break;
      case LEFT: next.node.left = addNode(val); break;
      case RIGHT: next.node.right = addNode(val); break;
      }
    }
    return root;
  } 

  /** Create a new node and queue up locations for its future children. */
  Node addNode(int val) {
    Node node = new Node(val);
    queue.addLast(new Location(node, Location.Kind.LEFT));
    queue.addLast(new Location(node, Location.Kind.RIGHT));
    return node;
  }

  static class Node {
    final int val;
    Node left, right;
    Node(int val) {
      this.val = val;
    }
    void print(int level) {
      System.out.format("%" + level + "s%d\n", "", val);
      if (left != null) left.print(level + 1);
      if (right != null) right.print(level + 1);
    }
  }

  static class Location {
    enum Kind { ROOT, LEFT, RIGHT }
    final Node node;
    final Kind kind;
    Location(Node node, Kind kind) {
      this.node = node;
      this.kind = kind;
    }
  }

  public static void main(String [] args) {
    int [] vals = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
    new CompleteBinaryTree().build(vals).print(1);
  }
}