Просто интересно, могу ли я получить несколько советов по печати красивого бинарного дерева в виде:
5
10
11
7
6
3
4
2
Прямо сейчас то, что это печатает, является:
2
4
3
6
7
11
10
5
Я знаю, что мой пример переворачивается с того, что я сейчас печатаю, и это не имеет значения, если я печатаю из корня вниз, как сейчас печатает. Любые советы очень важны для моего полного вопроса:
Как мне изменить мои отпечатки, чтобы дерево выглядело как дерево?
//Binary Search Tree Program
#include <iostream>
#include <cstdlib>
#include <queue>
using namespace std;
int i = 0;
class BinarySearchTree
{
private:
struct tree_node
{
tree_node* left;
tree_node* right;
int data;
};
tree_node* root;
public:
BinarySearchTree()
{
root = NULL;
}
bool isEmpty() const { return root==NULL; }
void print_inorder();
void inorder(tree_node*);
void print_preorder();
void preorder(tree_node*);
void print_postorder();
void postorder(tree_node*);
void insert(int);
void remove(int);
};
// Smaller elements go left
// larger elements go right
void BinarySearchTree::insert(int d)
{
tree_node* t = new tree_node;
tree_node* parent;
t->data = d;
t->left = NULL;
t->right = NULL;
parent = NULL;
// is this a new tree?
if(isEmpty()) root = t;
else
{
//Note: ALL insertions are as leaf nodes
tree_node* curr;
curr = root;
// Find the Node's parent
while(curr)
{
parent = curr;
if(t->data > curr->data) curr = curr->right;
else curr = curr->left;
}
if(t->data < parent->data)
{
parent->left = t;
}
else
{
parent->right = t;
}
}
}
void BinarySearchTree::remove(int d)
{
//Locate the element
bool found = false;
if(isEmpty())
{
cout<<" This Tree is empty! "<<endl;
return;
}
tree_node* curr;
tree_node* parent;
curr = root;
while(curr != NULL)
{
if(curr->data == d)
{
found = true;
break;
}
else
{
parent = curr;
if(d>curr->data) curr = curr->right;
else curr = curr->left;
}
}
if(!found)
{
cout<<" Data not found! "<<endl;
return;
}// 3 cases :
// 1. We're removing a leaf node
// 2. We're removing a node with a single child
// 3. we're removing a node with 2 children
// Node with single child
if((curr->left == NULL && curr->right != NULL) || (curr->left != NULL && curr->right == NULL))
{
if(curr->left == NULL && curr->right != NULL)
{
if(parent->left == curr)
{
parent->left = curr->right;
delete curr;
}
else
{
parent->right = curr->left;
delete curr;
}
}
return;
}
//We're looking at a leaf node
if( curr->left == NULL && curr->right == NULL)
{
if(parent->left == curr)
{
parent->left = NULL;
}
else
{
parent->right = NULL;
}
delete curr;
return;
}//Node with 2 children
// replace node with smallest value in right subtree
if (curr->left != NULL && curr->right != NULL)
{
tree_node* chkr;
chkr = curr->right;
if((chkr->left == NULL) && (chkr->right == NULL))
{
curr = chkr;
delete chkr;
curr->right = NULL;
}
else // right child has children
{
//if the node's right child has a left child
// Move all the way down left to locate smallest element
if((curr->right)->left != NULL)
{
tree_node* lcurr;
tree_node* lcurrp;
lcurrp = curr->right;
lcurr = (curr->right)->left;
while(lcurr->left != NULL)
{
lcurrp = lcurr;
lcurr = lcurr->left;
}
curr->data = lcurr->data;
delete lcurr;
lcurrp->left = NULL;
}
else
{
tree_node* tmp;
tmp = curr->right;
curr->data = tmp->data;
curr->right = tmp->right;
delete tmp;
}
}
return;
}
}
void BinarySearchTree::print_postorder()
{
postorder(root);
}
void BinarySearchTree::postorder(tree_node* p)
{
if(p != NULL)
{
if(p->left) postorder(p->left);
if(p->right) postorder(p->right);
cout<<" "<<p->data<<"\n ";
}
else return;
}
int main()
{
BinarySearchTree b;
int ch,tmp,tmp1;
while(1)
{
cout<<endl<<endl;
cout<<" Binary Search Tree Operations "<<endl;
cout<<" ----------------------------- "<<endl;
cout<<" 1. Insertion/Creation "<<endl;
cout<<" 2. Printing "<<endl;
cout<<" 3. Removal "<<endl;
cout<<" 4. Exit "<<endl;
cout<<" Enter your choice : ";
cin>>ch;
switch(ch)
{
case 1 : cout<<" Enter Number to be inserted : ";
cin>>tmp;
b.insert(tmp);
i++;
break;
case 2 : cout<<endl;
cout<<" Printing "<<endl;
cout<<" --------------------"<<endl;
b.print_postorder();
break;
case 3 : cout<<" Enter data to be deleted : ";
cin>>tmp1;
b.remove(tmp1);
break;
case 4:
return 0;
}
}
}
Чтобы рекурсивно распечатывать дерево, вам нужно передать два аргумента вашей функции печати:
Например, вы можете сделать это:
void BinarySearchTree::postorder(tree_node* p, int indent=0)
{
if(p != NULL) {
if(p->left) postorder(p->left, indent+4);
if(p->right) postorder(p->right, indent+4);
if (indent) {
std::cout << std::setw(indent) << ' ';
}
cout<< p->data << "\n ";
}
}
Первоначальный вызов должен быть postorder(root);
Если вы хотите напечатать дерево с корнем вверху, переместите cout
к вершине if
,
void btree::postorder(node* p, int indent)
{
if(p != NULL) {
if(p->right) {
postorder(p->right, indent+4);
}
if (indent) {
std::cout << std::setw(indent) << ' ';
}
if (p->right) std::cout<<" /\n" << std::setw(indent) << ' ';
std::cout<< p->key_value << "\n ";
if(p->left) {
std::cout << std::setw(indent) << ' ' <<" \\\n";
postorder(p->left, indent+4);
}
}
}
С этим деревом:
btree *mytree = new btree();
mytree->insert(2);
mytree->insert(1);
mytree->insert(3);
mytree->insert(7);
mytree->insert(10);
mytree->insert(2);
mytree->insert(5);
mytree->insert(8);
mytree->insert(6);
mytree->insert(4);
mytree->postorder(mytree->root);
Приведет к такому результату:
Этого никогда не будет достаточно, если только кто-нибудь не вернётся назад, чтобы откалибровать вывод на дисплей. Но с помощью эвристики можно эффективно генерировать достаточно двоичных деревьев: учитывая высоту дерева, можно догадаться, какова ожидаемая ширина и множество узлов на разных глубинах.
Для этого нужно несколько штук, поэтому давайте начнем с функций более высокого уровня, чтобы обеспечить контекст.
Красивая функция печати:
// create a pretty vertical tree
void postorder(Node *p)
{
int height = getHeight(p) * 2;
for (int i = 0 ; i < height; i ++) {
printRow(p, height, i);
}
}
Приведенный выше код прост. Основная логика в функции printRow. Давайте углубимся в это.
void printRow(const Node *p, const int height, int depth)
{
vector<int> vec;
getLine(p, depth, vec);
cout << setw((height - depth)*2); // scale setw with depth
bool toggle = true; // start with left
if (vec.size() > 1) {
for (int v : vec) {
if (v != placeholder) {
if (toggle)
cout << "/" << " ";
else
cout << "\\" << " ";
}
toggle = !toggle;
}
cout << endl;
cout << setw((height - depth)*2);
}
for (int v : vec) {
if (v != placeholder)
cout << v << " ";
}
cout << endl;
}
getLine () делает то, что вы ожидаете: он сохраняет все узлы с заданной равной глубиной в vec. Вот код для этого:
void getLine(const Node *root, int depth, vector<int>& vals)
{
if (depth <= 0 && root != nullptr) {
vals.push_back(root->val);
return;
}
if (root->left != nullptr)
getLine(root->left, depth-1, vals);
else if (depth-1 <= 0)
vals.push_back(placeholder);
if (root->right != nullptr)
getLine(root->right, depth-1, vals);
else if (depth-1 <= 0)
vals.push_back(placeholder);
}
Теперь вернемся к printRow (). Для каждой строки мы устанавливаем ширину потока на основе того, насколько глубоко мы находимся в двоичном дереве. Это форматирование будет хорошим, потому что, как правило, чем глубже вы идете, тем больше ширина нужна. Обычно я говорю, потому что на вырожденных деревьях это не выглядело бы так красиво. Пока дерево грубо сбалансировано и мало (< 20 штук), должно получиться нормально.
Заполнитель необходим для правильного выравнивания символов ‘/’ и ‘\’. Поэтому, когда строка получается с помощью getLine (), мы вставляем заполнитель, если на указанной глубине нет ни одного узла. Заполнитель может быть установлен как угодно (1<<31)
например. Очевидно, что это не надежно, потому что заполнитель может быть допустимым значением узла. Если кодер получил «спанк» и имеет дело только с десятичными числами, можно изменить код так, чтобы он генерировал строки с десятичным преобразованием через getLine (), и использовал заполнитель, такой как «_». (К сожалению, я не такой кодер: P)
Результат для следующих элементов, вставленных в порядке: 8, 12, 4, 2, 5, 15:
8
/ \
4 12
/ \ \
2 5 15
getHeight () предоставляется читателю в качестве упражнения. 🙂
Можно даже получить более хорошие результаты, задним числом обновляя набор мелких узлов на основе количества элементов в более глубоких узлах.
Это тоже оставлено читателю в качестве упражнения.
//Binary tree (pretty print):
// ________________________50______________________
// ____________30 ____________70__________
// ______20____ 60 ______90
// 10 15 80// prettyPrint
public static void prettyPrint(BTNode node) {
// get height first
int height = heightRecursive(node);
// perform level order traversal
Queue<BTNode> queue = new LinkedList<BTNode>();
int level = 0;
final int SPACE = 6;
int nodePrintLocation = 0;
// special node for pushing when a node has no left or right child (assumption, say this node is a node with value Integer.MIN_VALUE)
BTNode special = new BTNode(Integer.MIN_VALUE);
queue.add(node);
queue.add(null); // end of level 0
while(! queue.isEmpty()) {
node = queue.remove();
if (node == null) {
if (!queue.isEmpty()) {
queue.add(null);
}
// start of new level
System.out.println();
level++;
} else {
nodePrintLocation = ((int) Math.pow(2, height - level)) * SPACE;
System.out.print(getPrintLine(node, nodePrintLocation));
if (level < height) {
// only go till last level
queue.add((node.left != null) ? node.left : special);
queue.add((node.right != null) ? node.right : special);
}
}
}
}
public void prettyPrint() {
System.out.println("\nBinary tree (pretty print):");
prettyPrint(root);
}
private static String getPrintLine(BTNode node, int spaces) {
StringBuilder sb = new StringBuilder();
if (node.data == Integer.MIN_VALUE) {
// for child nodes, print spaces
for (int i = 0; i < 2 * spaces; i++) {
sb.append(" ");
}
return sb.toString();
}
int i = 0;
int to = spaces/2;
for (; i < to; i++) {
sb.append(' ');
}
to += spaces/2;
char ch = ' ';
if (node.left != null) {
ch = '_';
}
for (; i < to; i++) {
sb.append(ch);
}
String value = Integer.toString(node.data);
sb.append(value);
to += spaces/2;
ch = ' ';
if (node.right != null) {
ch = '_';
}
for (i += value.length(); i < to; i++) {
sb.append(ch);
}
to += spaces/2;
for (; i < to; i++) {
sb.append(' ');
}
return sb.toString();
}
private static int heightRecursive(BTNode node) {
if (node == null) {
// empty tree
return -1;
}
if (node.left == null && node.right == null) {
// leaf node
return 0;
}
return 1 + Math.max(heightRecursive(node.left), heightRecursive(node.right));
}
Если вам нужно только визуализировать ваше дерево, лучшим способом было бы вывести его в точечный формат и нарисовать его с помощью grapviz.
Вы можете посмотреть на точка руководство для получения дополнительной информации о синтаксисе и т. д.
#include <stdio.h>
#include <stdlib.h>
struct Node
{
struct Node *left,*right;
int val;
} *root=NULL;
int rec[1000006];
void addNode(int,struct Node*);
void printTree(struct Node* curr,int depth)
{
int i;
if(curr==NULL)return;
printf("\t");
for(i=0;i<depth;i++)
if(i==depth-1)
printf("%s\u2014\u2014\u2014",rec[depth-1]?"\u0371":"\u221F");
else
printf("%s ",rec[i]?"\u23B8":" ");
printf("%d\n",curr->val);
rec[depth]=1;
printTree(curr->left,depth+1);
rec[depth]=0;
printTree(curr->right,depth+1);
}
int main()
{
root=(struct Node*)malloc(sizeof(struct Node));
root->val=50;
//addNode(50,root);
addNode(75,root); addNode(25,root);
addNode(15,root); addNode(30,root);
addNode(100,root); addNode(60,root);
addNode(27,root); addNode(31,root);
addNode(101,root); addNode(99,root);
addNode(5,root); addNode(61,root);
addNode(55,root); addNode(20,root);
addNode(0,root); addNode(21,root);
//deleteNode(5,root);
printTree(root,0);
return 0;
}
void addNode(int v,struct Node* traveller)
{
struct Node *newEle=(struct Node*)malloc(sizeof(struct Node));
newEle->val=v;
for(;;)
{
if(v<traveller->val)
{
if(traveller->left==NULL){traveller->left=newEle;return;}
traveller=traveller->left;
}
else if(v>traveller->val)
{
if(traveller->right==NULL){traveller->right=newEle;return;}
traveller=traveller->right;
}
else
{
printf("%d Input Value is already present in the Tree !!!\n",v);
return;
}
}
}
Надеюсь, ты находишь это довольно …
Выход:
50
ͱ———25
⎸ ͱ———15
⎸ ⎸ ͱ———5
⎸ ⎸ ⎸ ͱ———0
⎸ ⎸ ∟———20
⎸ ⎸ ∟———21
⎸ ∟———30
⎸ ͱ———27
⎸ ∟———31
∟———75
ͱ———60
⎸ ͱ———55
⎸ ∟———61
∟———100
ͱ———99
∟———101
Вот небольшой пример печати кучи на основе массива в виде дерева. Для больших чисел нужно немного подстроиться под алгоритм. Я просто сделал сетку на бумаге и выяснил, какой индекс пространства будет выглядеть для каждого узла, а затем заметил, что существует определенное количество пространства для каждого узла, основанное на количестве мест в родительском элементе и уровне рекурсии, а также на том, как высокое дерево. Это решение выходит за рамки простой печати в порядке следования и удовлетворяет требованию «красоты».
#include <iostream>
#include <vector>
static const int g_TerminationNodeValue = -999;
class HeapJ
{
public:
HeapJ(int* pHeapArray, int numElements)
{
m_pHeapPointer = pHeapArray;
m_numElements = numElements;
m_treeHeight = GetTreeHeight(1);
}
void Print()
{
m_printVec.clear();
int initialIndex = 0;
for(int i=1; i<m_treeHeight; ++i)
{
int powerOfTwo = 1;
for(int j=0; j<i; ++j)
{
powerOfTwo *= 2;
}
initialIndex += powerOfTwo - (i-1);
}
DoPrintHeap(1,0,initialIndex);
for(size_t i=0; i<m_printVec.size(); ++i)
{
std::cout << m_printVec[i] << '\n' << '\n';
}
}
private:
int* m_pHeapPointer;
int m_numElements;
int m_treeHeight;
std::vector<std::string> m_printVec;
int GetTreeHeight(int index)
{
const int value = m_pHeapPointer[index-1];
if(value == g_TerminationNodeValue)
{
return -1;
}
const int childIndexLeft = 2*index;
const int childIndexRight = childIndexLeft+1;
int valLeft = 0;
int valRight = 0;
if(childIndexLeft <= m_numElements)
{
valLeft = GetTreeHeight(childIndexLeft);
}
if(childIndexRight <= m_numElements)
{
valRight = GetTreeHeight(childIndexRight);
}
return std::max(valLeft,valRight)+1;
}
void DoPrintHeap(int index, size_t recursionLevel, int numIndents)
{
const int value = m_pHeapPointer[index-1];
if(value == g_TerminationNodeValue)
{
return;
}
if(m_printVec.size() == recursionLevel)
{
m_printVec.push_back(std::string(""));
}
const int numLoops = numIndents - (int)m_printVec[recursionLevel].size();
for(int i=0; i<numLoops; ++i)
{
m_printVec[recursionLevel].append(" ");
}
m_printVec[recursionLevel].append(std::to_string(value));
const int childIndexLeft = 2*index;
const int childIndexRight = childIndexLeft+1;
const int exponent = m_treeHeight-(recursionLevel+1);
int twoToPower = 1;
for(int i=0; i<exponent; ++i)
{
twoToPower *= 2;
}
const int recursionAdjust = twoToPower-(exponent-1);
if(childIndexLeft <= m_numElements)
{
DoPrintHeap(childIndexLeft, recursionLevel+1, numIndents-recursionAdjust);
}
if(childIndexRight <= m_numElements)
{
DoPrintHeap(childIndexRight, recursionLevel+1, numIndents+recursionAdjust);
}
}
};
const int g_heapArraySample_Size = 14;
int g_heapArraySample[g_heapArraySample_Size] = {16,14,10,8,7,9,3,2,4,1,g_TerminationNodeValue,g_TerminationNodeValue,g_TerminationNodeValue,0};
int main()
{
HeapJ myHeap(g_heapArraySample,g_heapArraySample_Size);
myHeap.Print();
return 0;
}
/* output looks like this:
16
14 10
8 7 9 3
2 4 1 0
*/
Сделайте обход в порядке, спускаясь к детям, прежде чем перейти к братьям и сестрам. На каждом уровне, то есть когда вы спускаетесь к ребенку, увеличивайте отступ. После каждого выведенного узла выведите новую строку.
Какой-то псевдокод. Вызов Print
с корнем вашего дерева.
void PrintNode(int indent, Node* node)
{
while (--indent >= 0)
std::cout << " ";
std::cout << node->value() << "\n";
}
void PrintNodeChildren(int indent, Node* node)
{
for (int child = 0; child < node->ChildCount(); ++child)
{
Node* childNode = node->GetChild(child);
PrintNode(indent, childNode);
PrintNodeChildren(indent + 1, childNode);
}
}
void Print(Node* root)
{
int indent = 0;
PrintNode(indent, root);
PrintNodeChildren(indent + 1, root);
}