Want to print out a “pretty” btree

此生再无相见时 提交于 2020-02-02 11:37:26

问题


As of now,this program traverses in level order but just prints out the numbers.I want to know how to print it so it could look something like the picture below or just a fancy way to show the different levels of the tree and its numbers.

       num1
      /    \
num2,num3  num4,num5

The thing I dont understand is how to tell which numbers are suppose to go into there respective level.Here is the code:

// C++ program for B-Tree insertion
#include<iostream>
#include <queue>
using namespace std;
int ComparisonCount = 0;
// A BTree node
class BTreeNode
{
    int *keys;  // An array of keys
    int t;      // Minimum degree (defines the range for number of keys)
    BTreeNode **C; // An array of child pointers
    int n;     // Current number of keys
    bool leaf; // Is true when node is leaf. Otherwise false
public:
    BTreeNode(int _t, bool _leaf);   // Constructor

                                     // A utility function to insert a new key in the subtree rooted with
                                     // this node. The assumption is, the node must be non-full when this
                                     // function is called
    void insertNonFull(int k);

    // A utility function to split the child y of this node. i is index of y in
    // child array C[].  The Child y must be full when this function is called
    void splitChild(int i, BTreeNode *y);

    // A function to traverse all nodes in a subtree rooted with this node
    void traverse();

    // A function to search a key in subtree rooted with this node.
    BTreeNode *search(int k);   // returns NULL if k is not present.

                                // Make BTree friend of this so that we can access private members of this
                                // class in BTree functions
    friend class BTree;
};

// A BTree
class BTree
{
    BTreeNode *root; // Pointer to root node
    int t;  // Minimum degree
public:
    // Constructor (Initializes tree as empty)
    BTree(int _t)
    {
        root = NULL;  t = _t;
    }

    // function to traverse the tree
    void traverse()
    {
        if (root != NULL) root->traverse();
    }

    // function to search a key in this tree
    BTreeNode* search(int k)
    {
        return (root == NULL) ? NULL : root->search(k);
    }

    // The main function that inserts a new key in this B-Tree
    void insert(int k);
};

// Constructor for BTreeNode class
BTreeNode::BTreeNode(int t1, bool leaf1)
{
    // Copy the given minimum degree and leaf property
    t = t1;
    leaf = leaf1;

    // Allocate memory for maximum number of possible keys
    // and child pointers
    keys = new int[2 * t - 1];
    C = new BTreeNode *[2 * t];

    // Initialize the number of keys as 0
    n = 0;
}

// Function to traverse all nodes in a subtree rooted with this node
/*void BTreeNode::traverse()
{
// There are n keys and n+1 children, travers through n keys
// and first n children
int i;
for (i = 0; i < n; i++)
{
// If this is not leaf, then before printing key[i],
// traverse the subtree rooted with child C[i].
if (leaf == false)
{
ComparisonCount++;
C[i]->traverse();
}
cout << " " << keys[i];
}

// Print the subtree rooted with last child
if (leaf == false)
{
ComparisonCount++;
C[i]->traverse();
}
}*/

// Function to search key k in subtree rooted with this node
BTreeNode *BTreeNode::search(int k)
{
    // Find the first key greater than or equal to k
    int i = 0;
    while (i < n && k > keys[i])
        i++;

    // If the found key is equal to k, return this node
    if (keys[i] == k)
    {
        ComparisonCount++;
        return this;
    }
    // If key is not found here and this is a leaf node
    if (leaf == true)
    {
        ComparisonCount++;
        return NULL;
    }

    // Go to the appropriate child
    return C[i]->search(k);
}

// The main function that inserts a new key in this B-Tree
void BTree::insert(int k)
{
    // If tree is empty
    if (root == NULL)
    {
        ComparisonCount++;
        // Allocate memory for root
        root = new BTreeNode(t, true);
        root->keys[0] = k;  // Insert key
        root->n = 1;  // Update number of keys in root
    }
    else // If tree is not empty
    {
        // If root is full, then tree grows in height
        if (root->n == 2 * t - 1)
        {
            ComparisonCount++;
            // Allocate memory for new root
            BTreeNode *s = new BTreeNode(t, false);

            // Make old root as child of new root
            s->C[0] = root;

            // Split the old root and move 1 key to the new root
            s->splitChild(0, root);

            // New root has two children now.  Decide which of the
            // two children is going to have new key
            int i = 0;
            if (s->keys[0] < k)
            {
                ComparisonCount++;
                i++;
            }s->C[i]->insertNonFull(k);

            // Change root
            root = s;
        }
        else  // If root is not full, call insertNonFull for root
            root->insertNonFull(k);
    }
}

// A utility function to insert a new key in this node
// The assumption is, the node must be non-full when this
// function is called
void BTreeNode::insertNonFull(int k)
{
    // Initialize index as index of rightmost element
    int i = n - 1;

    // If this is a leaf node
    if (leaf == true)
    {
        ComparisonCount++;
        // The following loop does two things
        // a) Finds the location of new key to be inserted
        // b) Moves all greater keys to one place ahead
        while (i >= 0 && keys[i] > k)
        {
            keys[i + 1] = keys[i];
            i--;
        }

        // Insert the new key at found location
        keys[i + 1] = k;
        n = n + 1;
    }
    else // If this node is not leaf
    {
        // Find the child which is going to have the new key
        while (i >= 0 && keys[i] > k)
            i--;

        // See if the found child is full
        if (C[i + 1]->n == 2 * t - 1)
        {
            ComparisonCount++;
            // If the child is full, then split it
            splitChild(i + 1, C[i + 1]);

            // After split, the middle key of C[i] goes up and
            // C[i] is splitted into two.  See which of the two
            // is going to have the new key
            if (keys[i + 1] < k)
                i++;
        }
        C[i + 1]->insertNonFull(k);
    }
}

// A utility function to split the child y of this node
// Note that y must be full when this function is called
void BTreeNode::splitChild(int i, BTreeNode *y)
{
    // Create a new node which is going to store (t-1) keys
    // of y
    BTreeNode *z = new BTreeNode(y->t, y->leaf);
    z->n = t - 1;

    // Copy the last (t-1) keys of y to z
    for (int j = 0; j < t - 1; j++)
        z->keys[j] = y->keys[j + t];

    // Copy the last t children of y to z
    if (y->leaf == false)
    {
        ComparisonCount++;
        for (int j = 0; j < t; j++)
            z->C[j] = y->C[j + t];
    }

    // Reduce the number of keys in y
    y->n = t - 1;

    // Since this node is going to have a new child,
    // create space of new child
    for (int j = n; j >= i + 1; j--)
        C[j + 1] = C[j];

    // Link the new child to this node
    C[i + 1] = z;

    // A key of y will move to this node. Find location of
    // new key and move all greater keys one space ahead
    for (int j = n - 1; j >= i; j--)
        keys[j + 1] = keys[j];

    // Copy the middle key of y to this node
    keys[i] = y->keys[t - 1];

    // Increment count of keys in this node
    n = n + 1;
}
void BTreeNode::traverse()
{
    std::queue<BTreeNode*> queue;
    queue.push(this);
    while (!queue.empty())
    {
        BTreeNode* current = queue.front();
        queue.pop();
        int i;
        for (i = 0; i < current->n; i++)  //*
        {
            if (current->leaf == false)  //*
            {
                ComparisonCount++;
                queue.push(current->C[i]);
            }cout << " " << current->keys[i] << endl;
        }
        if (current->leaf == false)  //*
        {
            ComparisonCount++;
            queue.push(current->C[i]);
        }
    }
}

// Driver program to test above functions
int main()
{
    BTree t(4); // A B-Tree with minium degree 4
    srand(29324);
    for (int i = 0; i<10; i++)
    {
        int p = rand() % 10000;
        t.insert(p);
    }

    cout << "Traversal of the constucted tree is "<<endl;
    t.traverse();

    int k = 6;
    (t.search(k) != NULL) ? cout << "\nPresent" : cout << "\nNot Present" << endl;

    k = 28;
    (t.search(k) != NULL) ? cout << "\nPresent" : cout << "\nNot Present" << endl;

    cout << "There are " << ComparisonCount << " comparisons." << endl;
    system("pause");
    return 0;
}

回答1:


First of all, Janus Troelsen's answer in the topic Is there a way to draw B-Trees on Graphviz? shows an elegant way of creating professional B-tree drawings like those used in the Wikipedia, either online by pasting stuff into the web interface that he linked, or by using a local copy of GraphViz. The format of the required text files is extremely simple and easy to generate with a standard traversal of a B-tree. Patrick Kreutzer has gathered everything together in a nice overview titled How to draw a B-Tree using Dot.

However, for debugging and studying a B-tree implementation under development it can be extremely helpful to have a simple means of rendering a B-tree as text. In the following I'll give a simple C++ class that can draw nodes centred above their children like this:

## inserting 42...

         [56 64 86]

[37 42] [62] [68 72] [95 98]

## inserting 96...

              [64]

    [56]            [86]

[37 42] [62]  [68 72] [95 96 98]

This was taken from actual output for the B-tree code in the previous topic, after changing the modulus in the rand() call to 100 in order to get smaller numbers (easier to take in at a glance than nodes full of longer numbers) and constructing a B-tree with t = 2.

The fundamental problem here is that the information needed for centring a node - the starting position of the left-most grandchild and the ending position of the right-most grandchild - only becomes available during a traversal of the subtree. Hence I chose the approach of doing a full traversal of the tree and storing everything that is needed for printing: the node texts and the min/max position information.

Here is the declaration of the class, with a bit of uninteresting stuff inlined to get it out of the way:

class BTreePrinter
{
   struct NodeInfo
   {
      std::string text;
      unsigned text_pos, text_end;  // half-open range
   };

   typedef std::vector<NodeInfo> LevelInfo;

   std::vector<LevelInfo> levels;

   std::string node_text (int const keys[], unsigned key_count);

   void before_traversal ()
   {
      levels.resize(0);
      levels.reserve(10);   // far beyond anything that could usefully be printed
   }

   void visit (BTreeNode const *node, unsigned level = 0, unsigned child_index = 0);

   void after_traversal ();

public:
   void print (BTree const &tree)
   {
      before_traversal();
      visit(tree.root);
      after_traversal();
   }
};

This class needs to be a friend of BTreeNode and BTree in order to get the privileged access that it needs. A lot of production-quality noise was elided in order to make things compact and simple for this exposition, starting with the removal of all assert() calls that my fingers automatically inserted while writing the class...

Here is the first interesting bit, the collection of all node texts and positioning information via a full traversal of the tree:

void BTreePrinter::visit (BTreeNode const *node, unsigned level, unsigned child_index)
{
   if (level >= levels.size())
      levels.resize(level + 1);

   LevelInfo &level_info = levels[level];
   NodeInfo info;

   info.text_pos = 0;
   if (!level_info.empty())  // one blank between nodes, one extra blank if left-most child
      info.text_pos = level_info.back().text_end + (child_index == 0 ? 2 : 1);

   info.text = node_text(node->keys, unsigned(node->n));

   if (node->leaf)
   {
      info.text_end = info.text_pos + unsigned(info.text.length());
   }
   else // non-leaf -> do all children so that .text_end for the right-most child becomes known
   {
      for (unsigned i = 0, e = unsigned(node->n); i <= e; ++i)  // one more pointer than there are keys
         visit(node->C[i], level + 1, i);

      info.text_end = levels[level + 1].back().text_end;
   }

   levels[level].push_back(info);
}

The most relevant fact regarding the layout logic is that a given node 'owns' (covers) all of the horizontal space covered by itself and all of its descendants; the start of the range for a node is the end of the range of its left neighbour plus one or two blanks, depending on whether the left neighbour is a sibling or merely a cousin. The end of the range for a node only becomes known after the traversal of the full subtree, at which point it can be looked up by looking at the end of the right-most child.

The code that dumps a node as text would normally be found in the orbit of the BTreeNode class; for this post I've added it to the printer class:

std::string BTreePrinter::node_text (int const keys[], unsigned key_count)
{
   std::ostringstream os;
   char const *sep = "";

   os << "[";
   for (unsigned i = 0; i < key_count; ++i, sep = " ")
      os << sep << keys[i];
   os << "]";

   return os.str();
}

Here's a little helper that needs to be stuffed somewhere:

void print_blanks (unsigned n)
{
   while (n--)
      std::cout << ' ';
}

And here is the logic that prints all the information that was collected during a full traversal of the tree:

void BTreePrinter::after_traversal ()
{
   for (std::size_t l = 0, level_count = levels.size(); ; )
   {    
      auto const &level = levels[l];
      unsigned prev_end = 0;

      for (auto const &node: level)
      {         
         unsigned total = node.text_end - node.text_pos;
         unsigned slack = total - unsigned(node.text.length());
         unsigned blanks_before = node.text_pos - prev_end;

         print_blanks(blanks_before + slack / 2);
         std::cout << node.text;

         if (&node == &level.back())
            break;

         print_blanks(slack - slack / 2);

         prev_end += blanks_before + total;
      }

      if (++l == level_count)
         break;

      std::cout << "\n\n";
   }

   std::cout << "\n";
}

Finally, a version of the original B-tree code that uses this class:

BTreePrinter printer;
BTree t(2);

srand(29324);

for (unsigned i = 0; i < 15; ++i)
{
    int p = rand() % 100;
    std::cout << "\n## inserting " << p << "...\n\n";
    t.insert(p);
    printer.print(t);
}


来源:https://stackoverflow.com/questions/36829399/want-to-print-out-a-pretty-btree

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!