C# 绘制对称二叉B-树

C# 绘制对称二叉B-树,c#,binary-tree,b-tree,symmetric,C#,Binary Tree,B Tree,Symmetric,我有一个小“项目”,它涉及到绘制对称的二元B树,比如: 但我无法找到正确计算每个节点位置(x,y)的方法。我现在的做法是,随着树的高度增加,一些节点往往会被其他节点重叠 有人能告诉我如何计算节点的位置吗 我正在使用C#,这是我现在拥有的表示节点的类: class SBBTreeNode<T> where T : IComparable { public SBBTreeNode(T item) { Data = item;

我有一个小“项目”,它涉及到绘制对称的二元B树,比如:

但我无法找到正确计算每个节点位置(x,y)的方法。我现在的做法是,随着树的高度增加,一些节点往往会被其他节点重叠

有人能告诉我如何计算节点的位置吗

我正在使用C#,这是我现在拥有的表示节点的类:

class SBBTreeNode<T> where T : IComparable {

        public SBBTreeNode(T item) {
            Data = item;
            Left = null;
            Right = null;
        }

        public T Data { get; private set; }
        public SBBTreeNode<T> Left;
        public SBBTreeNode<T> Right;
        public bool IsHorizontal { get; set; } //Is this node horizontal?

        public bool IsLeaf() {
            return Left == null && Right == null;
        }
    }
类sbb重节点,其中T:i可比较{
公共SBB屏蔽(T项){
数据=项目;
左=空;
右=空;
}
公共T数据{get;私有集;}
公共SBB左重节点;
公共SBB重节点权;
public bool IsHorizontal{get;set;}//此节点是水平的吗?
公共布尔岛(){
返回Left==null&&Right==null;
}
}

我首先将根节点放在(0,0)(实际上,从哪里开始并不重要)。将此点称为(父对象X,父对象Y)。然后选择一个起始宽度(比如2^(树中的层数),如果您知道树有多少层,否则,只需选择任意宽度)

左边的子对象位于位置(父对象X-width/2,父对象Y-1),右边的子对象位于位置(父对象X+width/2,父对象Y-1)。然后将宽度更改为宽度=宽度/2。如果一个子节点恰好是水平的,您可以忽略父节点的Y-1部分,保留父节点的Y,然后在头节点的每个子节点上重复。每次向下移动一个关卡时,用宽度/2-ε替换宽度


希望这有帮助。

这里是一个绘图例程:

void drawTree(Graphics G)
{
    if (flatTree.Count <= 0) return;
    if (maxItemsPerRow <= 0) return;
    if (maxLevels <= 0) return;
    int width = (int)G.VisibleClipBounds.Width / (maxItemsPerRow + 2);
    int height = (int)G.VisibleClipBounds.Height / (maxLevels + 2);
    int side = width / 4;
    int textOffsetX = 3;
    int textOffsetY = 5;
    int graphOffsetY = 50;
    Size squaresize = new Size(side * 2, side * 2);

    foreach (SBBTreeNode<string> node in flatTree)
    {
        Point P0 = new Point(node.Col * width, node.Row * height + graphOffsetY);
        Point textPt = new Point(node.Col * width  + textOffsetX, 
                                    node.Row * height + textOffsetY + graphOffsetY);
        Point midPt = new Point(node.Col * width + side, 
                                node.Row * height + side + graphOffsetY);

        if (node.Left != null)
            G.DrawLine(Pens.Black, midPt, 
                new Point(node.Left.Col * width + side, 
                          node.Left.Row * height + side + graphOffsetY));
        if (node.Right != null)
            G.DrawLine(Pens.Black, midPt, 
                new Point(node.Right.Col * width + side, 
                          node.Right.Row * height + side + graphOffsetY));

        G.FillEllipse(Brushes.Beige, new Rectangle(P0, squaresize));
        G.DrawString(node.Data, Font, Brushes.Black, textPt);
        G.DrawEllipse(Pens.Black, new Rectangle(P0, squaresize));
    }
}
现在让我们来看看导致这一点的各种因素:

drawTree例程显然是由面板的绘制事件触发的

我使用了几个类级变量:

这是我在测试中构建的树;请注意,为了让事情变得简单一点,我已经为
string
转储了您的泛型类型
T

Dictionary<string, SBBTreeNode<string> > tree
        = new Dictionary<string, SBBTreeNode<string>>();
以下是使用队列创建平面树的方式:

List<SBBTreeNode<string>> FlatTree()
{
    List<SBBTreeNode<string>> flatTree = new List<SBBTreeNode<string>>();
    Queue<SBBTreeNode<string>> queue = new Queue<SBBTreeNode<string>>();

    queue.Enqueue((SBBTreeNode<string>)(tree[tree.Keys.First()]));
    flatNode(queue, flatTree);
    return flatTree;
}
我已经编写了AddNode例程,每个节点的级别都设置在这里

你肯定想/需要以不同的方式来做。简单的SetRows例程是快照,尤其是在使用flatTree进行横向搜索时:

void setRows()
{
    foreach (SBBTreeNode<string> node in flatTree)
    {
        if (node.Left != null)  node.Left.Row = node.Row + 1;
        if (node.Right != null) node.Right.Row = 
                                node.Row + 1 - (node.Right.IsHorizontal ? 1:0);
    }
}
void setRows()
{
foreach(flatTree中的SBBTreeNode节点)
{
如果(node.Left!=null)node.Left.Row=node.Row+1;
如果(node.Right!=null)node.Right.Row=
node.Row+1-(node.Right.IsHorizontal?1:0);
}
}
说明:

除了用于绘图的flatTree之外,解决方案的核心是SetCols例程

在平衡B-树中,在最后一行或倒数第二行达到最大宽度

这里我计算该行中的节点数。这给了我整棵树的宽度,maxItemsPerRow。该例程还将高度设置为maxLevels

现在,我首先设置最宽行中的列值,从左到右(如果存在,则设置最后一行中的悬垂儿童)

然后我一级一级地向上移动,并将每个Col值计算为左、右子级之间的中间值,始终注意水平节点


注意,我假设所有水平节点都是正确的子节点!如果这不是真的,您将不得不在FlatTree和setCol例程中进行各种自适应。

图像显示了一个非对称的B-树,我会说。.哇,我没想到会有这么完整的答案。非常感谢你。解释得也很好。
int maxItemsPerRow = 0;
int maxLevels = 0;
List<SBBTreeNode<string>> FlatTree()
{
    List<SBBTreeNode<string>> flatTree = new List<SBBTreeNode<string>>();
    Queue<SBBTreeNode<string>> queue = new Queue<SBBTreeNode<string>>();

    queue.Enqueue((SBBTreeNode<string>)(tree[tree.Keys.First()]));
    flatNode(queue, flatTree);
    return flatTree;
}
void flatNode(Queue<SBBTreeNode<string>> queue, List<SBBTreeNode<string>>flatTree)
{
    if (queue.Count == 0) return;

    SBBTreeNode<string> node = queue.Dequeue();
    if (!node.IsHorizontal) flatTree.Add(node);
    if (node.Left != null) { queue.Enqueue(node.Left); }
    if (node.Left != null && node.Left.Right != null && node.Left.Right.IsHorizontal) 
        queue.Enqueue(node.Left.Right);
    if (node.Right != null) 
    { 
        if (node.Right.IsHorizontal) flatTree.Add(node.Right);   
        else queue.Enqueue(node.Right); 
    }
    flatNode(queue, flatTree);
}
void setCols()
{
    List<SBBTreeNode<string>> FT = flatTree;
    int levelMax   = FT.Last().Row;
    int LMaxCount = FT.Count(n => n.Row == levelMax);
    int LMaxCount1 = FT.Count(n => n.Row == levelMax-1);
    if (LMaxCount1 > LMaxCount)
      { LMaxCount = LMaxCount1; levelMax = levelMax - 1; }

    int c = 1;
    foreach (SBBTreeNode<string> node in FT) if (node.Row == levelMax)
        {
            node.Col = ++c;
            if (node.Left != null) node.Left.Col = c - 1;
            if (node.Right != null) node.Right.Col = c + 1;
        }

    List<SBBTreeNode<string>> Exceptions = new List<SBBTreeNode<string>>();

    for (int n = FT.Count- 1; n >= 0; n--)
    {
       SBBTreeNode<string> node = FT[n];
       if (node.Row < levelMax)
       {
          if (node.IsHorizontal) node.Col = node.Left.Col + 1;
          else if ((node.Left == null) | (node.Right == null)) {Exceptions.Add(node);}
          else node.Col = (node.Left.Col + node.Right.Col) / 2;
       }
    }
    // partially filled nodes will need extra attention
    foreach (SBBTreeNode<string> node in Exceptions) 
                                 textBox1.Text += "\r\n >>>" + node.Data;
    maxLevels = levelMax;
    maxItemsPerRow =  LMaxCount;
}
public int Row { get; set; }
public int Col { get; set; }
void setRows()
{
    foreach (SBBTreeNode<string> node in flatTree)
    {
        if (node.Left != null)  node.Left.Row = node.Row + 1;
        if (node.Right != null) node.Right.Row = 
                                node.Row + 1 - (node.Right.IsHorizontal ? 1:0);
    }
}