简体   繁体   中英

Algorithm for drawing b-tree

I was looking for an algorithm to draw b-tree to a user control in C#, WinForms. I'm wondering whether there's some simple algorithm that can be used to draw lines between linked nodes, as this is my main problem. For drawing nodes I've used Knuth algorithm, because it's simple and takes only one inorder traversal and I require only one traversal. I'd really appreciate any resources, because I only found one which does traverse twice and requires heavy modification to node class which I'd love to avoid.

The only thing I've found (mentioned above) is http://www.cs.unc.edu/techreports/89-034.pdf , but this does not convince me at all.

What I have already is inorder traversal algorithm:

public void Traverse(BTree<TKey, TValue>.TraverseFunction function, ref int depth, ref int xPos)
    {
        depth++;

        int i = 0;
        for (i = 0; i < elements.Count; i++)
        {
            if (!isLeaf)
                children[i].Traverse(function, ref depth, ref xPos);

            function.Invoke(elements[i].Key, elements[i].Value, ref depth, ref xPos);
            xPos++;
        }

        if (!isLeaf)
            children[children.Count - 1].Traverse(function, ref depth, ref xPos);

        depth--;
    }

And drawing method that matches the TraverseFuction delegate:

 private void traverseFunction(int key, string value, ref int depth, ref int xPos)
    {
        float x, y;

        x = xPos * (horizontalSpacing + horizontalSize);
        y = depth * (verticalSpacing + verticalSize);

        RectangleF rect = new RectangleF(x, y, horizontalSize, verticalSize);

        if(g.VisibleClipBounds.IntersectsWith(rect))
        {
            if(depth != treeHeight)
            {
                g.FillRectangle(Brushes.Black, x, y, horizontalSize, verticalSize);
                g.DrawString(key.ToString(), SystemFonts.DefaultFont, Brushes.Gold, x + 2, y + 2);
            }
            else
            {
                g.FillRectangle(Brushes.Red, x, y, horizontalSize, verticalSize);
                g.DrawString(key.ToString(), SystemFonts.DefaultFont, Brushes.White, x + 2, y + 2);
            }
        }

        Console.WriteLine(key + " : " + depth);
    }

It is passed (in Paint event) to Tree traverse method that calls first method on root.

The problem is drawing the lines between linked nodes (keys actually).

I can use global variables (control scope). I wish to avoid several iterations and storing point - node within another class, but if that's not possible I'll "go" for it.

@EDIT

I decided to go with the algorithm from here:

http://billmill.org/pymag-trees/ (the most bottom one) and I can't figure it out, I tried to convert it to C#, but I'm stuck at

vol = vol.left()
vor = vor.right()

since there's no explanation of what Left() and Right() do, I thought they return the nearest Left and Right sibling, but I think it may not be the case, since it gives me null's when it should not (thus exiting the application).

@EDIT 2

I sorted it out, left() is basically what's nextLeft() from algorithms aboveL

def nextleft(tree):
if tree.thread:   return tree.thread
if tree.children: return tree.children[0]
else:             return None

Despite implementing this one I'd love to get any "faster" algorithm, since I don't really need that complicated algorithm and also it won't display keys, only nodes.

Speaking in functional programming lingo you have to fold the tree: process each node with the result from a previous node as part of the input to the processing.

So try passing the position of a parent node to its child as they are being traversed. This allows you to draw a line to the parent node from a child node without having to traverse twice.

I changed Traverse() to accept a Func :

public void Traverse<TResult>(
    Func<KeyValuePair<TKey, TValue>, TResult, int, int, TResult> traverser, 
    TResult parentResult,
    ref int depth, 
    ref int xPos)
{
    depth++;

    int i = 0;
    for (i = 0; i < elements.Count; i++)
    {
        // Traverse this node first.
        var traverseResult = traverser.Invoke(elements[i], parentResult, depth, xPos);

        // Now traverse children and pass result of own traversal.
        if (!isLeaf)
            children[i].Traverse(traverser, traverseResult, ref depth, ref xPos);

        xPos++;
    }

    if (!isLeaf)
        children[children.Count - 1].Traverse(function, traverseResult, ref depth, ref xPos);

    depth--;
}

You traverse function uses the parentPosition to draw a line and also has to return x and y of the current node at the end:

private Point traverseFunction(KeyValuePair<TKey, TValue> element, Point parentPosition, int depth, int xPos)
{
    float x, y;

    x = xPos * (horizontalSpacing + horizontalSize);
    y = depth * (verticalSpacing + verticalSize);

    RectangleF rect = new RectangleF(x, y, horizontalSize, verticalSize);

    if(g.VisibleClipBounds.IntersectsWith(rect))
    {
        if(depth != treeHeight)
        {
            g.FillRectangle(Brushes.Black, x, y, horizontalSize, verticalSize);
            g.DrawString(element.Key.ToString(), SystemFonts.DefaultFont, Brushes.Gold, x + 2, y + 2);
        }
        else
        {
            g.FillRectangle(Brushes.Red, x, y, horizontalSize, verticalSize);
            g.DrawString(element.Key.ToString(), SystemFonts.DefaultFont, Brushes.White, x + 2, y + 2);
        }

        // Use the parent node's position to draw a line.
        g.DrawLine(new Pen(Color.Black, 3), parentPosition, new Point(x, y));
    }

    Console.WriteLine(key + " : " + depth);
    return new Point(x, y);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM