想要打印出“漂亮”的btree

问题描述 投票:2回答:1

到目前为止,这个程序按层次顺序遍历,但只是打印出数字。我想知道如何打印它所以它看起来像下面的图片或只是一种奇特的方式来显示树的不同级别及其数字。

       num1
      /    \
num2,num3  num4,num5

我不明白的是如何分辨出哪些数字应该分别进入各个级别。这是代码:

// 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;
}
c++ b-tree
1个回答
2
投票

首先,Janus Troelsen's answer主题中的Is there a way to draw B-Trees on Graphviz?展示了一种优雅的方式来创建像维基百科中使用的专业B树图纸,可以通过将内容粘贴到他链接的网络界面或使用GraphViz的本地副本在线进行。所需文本文件的格式非常简单,并且易于使用B树的标准遍历生成。 Patrick Kreutzer在一个名为How to draw a B-Tree using Dot的精彩概述中聚集了所有内容。

但是,为了调试和研究正在开发的B树实现,使用一种简单的方法将B树渲染为文本非常有用。在下面我将给出一个简单的C ++类,它可以像这样绘制以子节点为中心的节点:

## 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]

这是从previous topic中的B树代码的实际输出中获取的,在将rand()调用中的模数更改为100以便获得更小的数字(比一个充满更长数字的节点更容易一瞥)并构建一个与t = 2的B树。

这里的基本问题是,在遍历子树期间,只有在节点中心所需的信息 - 最左边的孙子的起始位置和最右边的孙子的结束位置 - 才可用。因此,我选择了对树进行完全遍历并存储打印所需的所有内容的方法:节点文本和最小/最大位置信息。

这是该类的声明,内省了一些无趣的内容,以便将其排除在外:

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();
   }
};

此类需要成为BTreeNodeBTree的朋友才能获得所需的特权访问权限。为了使这个展览紧凑而简单,许多生产质量的噪音被删除,从删除所有assert()电话开始,我的手指在写课时自动插入...

这是第一个有趣的位,通过树的完整遍历收集所有节点文本和定位信息:

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);
}

关于布局逻辑的最相关的事实是给定节点'拥有'(覆盖)由其自身及其所有后代覆盖的所有水平空间;节点范围的开始是其左邻居范围的结尾加上一个或两个空白,具体取决于左邻居是兄弟姐妹还是仅仅是表兄弟。节点范围的结束仅在遍历完整子树之后才会被知道,此时可以通过查看最右边的子节点的结尾来查找它。

将节点转储为文本的代码通常可以在BTreeNode类的轨道中找到;这篇文章我把它添加到打印机类:

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();
}

这是一个需要在某处填充的小帮手:

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

以下是打印完全遍历树时收集的所有信息的逻辑:

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";
}

最后,使用此类的原始B树代码的一个版本:

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);
}
© www.soinside.com 2019 - 2024. All rights reserved.