Want to print out a “pretty” btree

百般思念 提交于 2019-12-06 05:23:24
DarthGizka

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