想要打印出一个 "pretty" btree
Want to print out a "pretty" btree
截至目前,该程序按级别顺序遍历,但只是打印出树的级别及其编号。
num1
/ \
num2,num3 num4,num5
我不明白的是如何判断哪些数字应该进入相应的 level.Here 是代码:
// 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;
}
首先,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.
但是,对于调试和研究开发中的 B 树实现而言,拥有一种将 B 树呈现为文本的简单方法非常有帮助。在下文中,我将给出一个简单的 C++ class,它可以像这样在子节点上方居中绘制节点:
## 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]
这取自 中 B 树代码的实际输出,在将 rand()
调用中的模数更改为 100 以获得更小的数字(更容易接受)之后一目了然比充满更长数字的节点)并用 t = 2
.
构建 B 树
这里的根本问题是,使节点居中所需的信息 - 最左边的孙子的起始位置和最右边的孙子的结束位置 - 仅在遍历子树期间可用。因此,我选择了对树进行完整遍历并存储打印所需的所有内容的方法:节点文本和 min/max 位置信息。
这是 class 的声明,内联了一些无趣的内容以使其不受影响:
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();
}
};
此 class 需要成为 BTreeNode
和 BTree
的好友才能获得所需的特权访问权限。为了使本次说明的内容紧凑和简单,删除了许多生产质量的噪音,首先删除了我的手指在编写 class 时自动插入的所有 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);
}
关于布局逻辑最相关的事实是给定节点 'owns'(覆盖)由其自身及其所有后代覆盖的所有水平 space;节点范围的起点是其左邻居范围的终点加上一个或两个空格,具体取决于左邻居是兄弟还是堂兄弟。只有在遍历完整子树后才知道节点范围的末尾,此时可以通过查看最右边子树的末尾来查找它。
将节点转储为文本的代码通常可以在 BTreeNode
class 的轨道中找到;为此 post 我已将它添加到打印机 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();
}
这里有一个小帮手需要塞到某处:
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";
}
最后,使用此 class:
的原始 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);
}
截至目前,该程序按级别顺序遍历,但只是打印出树的级别及其编号。
num1
/ \
num2,num3 num4,num5
我不明白的是如何判断哪些数字应该进入相应的 level.Here 是代码:
// 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;
}
首先,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.
但是,对于调试和研究开发中的 B 树实现而言,拥有一种将 B 树呈现为文本的简单方法非常有帮助。在下文中,我将给出一个简单的 C++ class,它可以像这样在子节点上方居中绘制节点:
## 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]
这取自 rand()
调用中的模数更改为 100 以获得更小的数字(更容易接受)之后一目了然比充满更长数字的节点)并用 t = 2
.
这里的根本问题是,使节点居中所需的信息 - 最左边的孙子的起始位置和最右边的孙子的结束位置 - 仅在遍历子树期间可用。因此,我选择了对树进行完整遍历并存储打印所需的所有内容的方法:节点文本和 min/max 位置信息。
这是 class 的声明,内联了一些无趣的内容以使其不受影响:
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();
}
};
此 class 需要成为 BTreeNode
和 BTree
的好友才能获得所需的特权访问权限。为了使本次说明的内容紧凑和简单,删除了许多生产质量的噪音,首先删除了我的手指在编写 class 时自动插入的所有 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);
}
关于布局逻辑最相关的事实是给定节点 'owns'(覆盖)由其自身及其所有后代覆盖的所有水平 space;节点范围的起点是其左邻居范围的终点加上一个或两个空格,具体取决于左邻居是兄弟还是堂兄弟。只有在遍历完整子树后才知道节点范围的末尾,此时可以通过查看最右边子树的末尾来查找它。
将节点转储为文本的代码通常可以在 BTreeNode
class 的轨道中找到;为此 post 我已将它添加到打印机 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();
}
这里有一个小帮手需要塞到某处:
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";
}
最后,使用此 class:
的原始 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);
}