Печатать двоичное дерево красиво

Просто интересно, могу ли я получить несколько советов по написанию красивого двоичного дерева в виде:

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

       }
    }
}

Ответ 1

Чтобы рекурсивно печатать дерево, вам нужно передать два аргумента в вашу функцию печати:

  • Дерево node для печати и
  • Уровень отступов

Например, вы можете сделать это:

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.

Ответ 2

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

приведет к такому результату:

enter image description here

Ответ 3

Этого никогда не будет достаточно, если не сделать некоторого возврата для повторной калибровки вывода на дисплей. Но можно эффективно генерировать достаточно бинарные деревья, используя эвристику. Учитывая высоту дерева, можно догадаться, что такое ожидаемая ширина и ширина узлов на разных глубинах. Для этого нужно несколько частей, поэтому сначала начните с функций более высокого уровня, чтобы обеспечить контекст.

Симпатичная функция печати:

   // 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(), мы вставляем заполнитель, если на указанной глубине нет node. Заполнитель может быть установлен как например (1<<31). Очевидно, что это не является надежным, потому что placeholder может быть действительным значением node. Если кодер получил spunk и имеет дело только с десятичными знаками, можно изменить код, чтобы испускать десятичные строки с помощью getLine() и использовать заполнитель типа "_". (К сожалению, я не такой кодер: P)

Результат для следующих элементов, вставленных в порядке: 8, 12, 4, 2, 5, 15 -

       8   
     /   \   
     4   12   
   /   \   \   
   2   5   15   

getHeight() остается читателем в качестве упражнения.:) Можно даже получить более красивые результаты, ретроактивно обновляя сетку мелких узлов на основе количества элементов в более глубоких узлах. Это тоже остается читателю как упражнение.

Ответ 4

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

Ответ 5

Если вам нужно только визуализировать ваше дерево, лучшим способом было бы вывести его в точечный формат и нарисовать его с помощью grapviz.

Вы можете посмотреть путеводитель для получения дополнительной информации о синтаксисе и т.д.

Ответ 6

#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

Ответ 7

Вот небольшой пример для печати массива на основе массива в виде дерева. Для больших чисел потребуется немного скорректировать алгоритм. Я просто сделал сетку на бумаге и выяснил, какой индекс пространства каждый node должен выглядеть красиво, а затем заметил, что существует шаблон для того, сколько пространств, каждое node необходимо на основе его родительского числа пробелов и уровня рекурсии а также как высока дерево. Это решение немного выходит за рамки печати в порядке уровня и удовлетворяет требованиям "красоты".

#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

*/

Ответ 8

Проведите обход в порядке, спустившись к детям, прежде чем переходить к братьям и сестрам. На каждом уровне, то есть когда вы спускаетесь к ребенку, увеличивайте отступ. После каждого выхода node напечатайте новую строку.

Некоторые psuedocode. Вызовите 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);  
}

Ответ 9

Вот еще одна реализация С++ 98 с выводом в виде tree.

Пример вывода:

PHP
└── is
    ├── minor
    │   └── perpetrated
    │       └── whereas
    │           └── skilled
    │               └── perverted
    │                   └── professionals.
    └── a
        ├── evil
        │   ├── incompetent
        │   │   ├── insidious
        │   │   └── great
        │   └── and
        │       ├── created
        │       │   └── by
        │       │       └── but
        │       └── amateurs
        └── Perl

Код:

void printTree(Node* root)
{
        if (root == NULL)
        {
                return;
        }

        cout << root->val << endl;
        printSubtree(root, "");
        cout << endl;
}

void printSubtree(Node* root, const string& prefix)
{
        if (root == NULL)
        {
                return;
        }

        bool hasLeft = (root->left != NULL);
        bool hasRight = (root->right != NULL);

        if (!hasLeft && !hasRight)
        {
                return;
        }

        cout << prefix;
        cout << ((hasLeft  && hasRight) ? "├── " : "");
        cout << ((!hasLeft && hasRight) ? "└── " : "");

        if (hasRight)
        {
                bool printStrand = (hasLeft && hasRight && (root->right->right != NULL || root->right->left != NULL));
                string newPrefix = prefix + (printStrand ? "│   " : "    ");
                cout << root->right->val << endl;
                printSubtree(root->right, newPrefix);
        }

        if (hasLeft)
        {
                cout << (hasRight ? prefix : "") << "└── " << root->left->val << endl;
                printSubtree(root->left, prefix + "    ");
        }
}

Ответ 10

предисловие

Поздний поздний ответ и на Java, но я хотел бы добавить свой в запись, потому что я узнал, как сделать это относительно легко, и способ, которым я это сделал, более важен. Хитрость заключается в том, чтобы понять, что вы действительно хотите, чтобы ни одно из ваших поддеревьев не было напечатано непосредственно под корневыми/корневыми узлами (в том же столбце). Почему вы можете спросить? Потому что это гарантирует, что нет проблем с пробелами, совпадений, невозможности столкновения левого поддерева и правого поддерева, даже с очень длинными числами. Он автоматически подстраивается под размер данных вашего узла. Основная идея состоит в том, чтобы левое поддерево печаталось полностью слева от вашего корня, а ваше правое поддерево печаталось полностью справа от вашего корня.

Анаология того, как я размышлял об этой проблеме

Хороший способ думать об этом с зонтиками. Во-первых, представьте, что вы снаружи с большим зонтиком, вы представляете корень и свой зонтик, и все под ним - все дерево. думайте о своем левом поддереве как о низком человеке (в любом случае, ниже вас) с меньшим зонтиком, который находится слева от вас под вашим большим зонтом. Ваше правое поддерево представлено похожим человеком с таким же меньшим зонтиком на правой стороне. Представьте, что если зонтики коротышек когда-либо касаются, они злятся и бьют друг друга (плохое совпадение). Вы корень, а люди рядом с вами ваши поддеревья. Вы должны быть точно в середине их зонтиков (поддеревьев), чтобы разбить двух мужчин и гарантировать, что они никогда не бьют зонтики. Хитрость заключается в том, чтобы затем представить это рекурсивно, когда каждый из двух мужчин имеет своих собственных двух меньших людей под своим зонтиком (дочерние узлы) с еще меньшими зонтиками (поддеревьями и т.д.), Которые им необходимо разделить под своими зонт (поддерево), они действуют как подпорки. По сути, это то, что должно произойти, чтобы "решить" общую проблему при печати бинарных деревьев, перекрытие поддеревьев. Чтобы сделать это, вам просто нужно подумать о том, как бы вы "напечатали" или "представили" мужчин в моей анальгии.

Моя реализация, ее ограничения и ее потенциал

Во-первых, единственная причина, по которой моя реализация кода принимает больше параметров, чем нужно (текущий узел должен быть напечатан, и уровень узла), заключается в том, что я не могу с легкостью перемещать строку вверх в консоли при печати, поэтому мне сначала нужно отобразить свои строки и напечатать их в обратном порядке. Для этого я создал lineLevelMap, который отображал каждую строку дерева на выход (это может быть полезно в будущем как способ легко собрать каждую строку дерева и одновременно распечатать ее).

//finds the height of the tree beforehand recursively, left to reader as exercise
int height = TreeHeight(root);
//the map that uses the height of the tree to detemrine how many entries it needs
//each entry maps a line number to the String of the actual line
HashMap<Integer,String> lineLevelMap = new HashMap<>();
//initialize lineLevelMap to have the proper number of lines for our tree 
//printout by starting each line as the empty string
for (int i = 0; i < height + 1; i++) {
    lineLevelMap.put(i,"");
} 

Если бы я мог получить управляющие коды ANSI, работающие в консоли java (windows ugh), я мог бы просто напечатать одну строку вверх и сократить число параметров на два, потому что мне не нужно было бы отображать линии или знать глубину дерева заранее. Независимо от того, вот мой код, который повторяется в порядке обхода дерева:

public int InOrderPrint(CalcTreeNode currentNode, HashMap<Integer,String> 
    lineLevelMap, int level, int currentIndent){
        //traverse left case
        if(currentNode.getLeftChild() != null){
            //go down one line
            level--;
            currentIndent = 
           InOrderPrint(currentNode.getLeftChild(),lineLevelMap,level,currentIndent);
            //go up one line
            level++;

    }
    //find the string length that already exists for this line
    int previousIndent = lineLevelMap.get(level).length();
    //create currentIndent - previousIndent spaces here
    char[] indent = new char[currentIndent-previousIndent];
    Arrays.fill(indent,' ');
    //actually append the nodeData and the proper indent to add on to the line 
    //correctly
    lineLevelMap.put(level,lineLevelMap.get(level).concat(new String(indent) + 
    currentNode.getData()));
    //update the currentIndent for all lines
    currentIndent += currentNode.getData().length();

    //traverse right case
    if (currentNode.getRightChild() != null){
        //go down one line
        level--;
        currentIndent = 
        InOrderPrint(currentNode.getRightChild(),lineLevelMap,level,currentIndent);
        //go up one line
        level++;
    }
    return currentIndent;
}

Чтобы на самом деле распечатать это дерево на консоли в Java, просто используйте LineMap, который мы сгенерировали. Таким образом, мы можем напечатать линии правой стороной вверх

for (int i = height; i > -1; i--) {
    System.out.println(lineLevelMap.get(i));
}

Как все это действительно работает

Подфункция InorderPrint выполняет всю "работу" и может рекурсивно распечатать любой узел, и он правильно работает с поддеревьями. Более того, он распределяет их равномерно, и вы можете легко изменить его, чтобы он равномерно распределял все узлы (просто сделайте Nodedata равными или заставьте алгоритмиста думать, что это так). Причина, по которой он работает так хорошо, состоит в том, что он использует длину данных узла, чтобы определить, где должен быть следующий отступ. Это гарантирует, что левое поддерево всегда печатается ПЕРЕД корнем и правым поддеревом, таким образом, если вы обеспечите это рекурсивно, ни один левый узел не будет напечатан под его корнем, ни его корень и т.д., И то же самое верно для любого правого узла. Вместо этого корень и все подчиненные корни находятся прямо в середине своих поддеревьев, и пространство не теряется.

Пример вывода со входом 3 + 2 выглядит в консоли так:

example of 3 + 2 in java console

И пример 3 + 4 * 5 + 6:

example of 3 + 4 * 5 + 6

И, наконец, пример (3 + 4) * (5 + 6) обратите внимание на круглые скобки:

( 3 + 4 ) * ( 5 + 6 ) example

Хорошо, но почему Inorder?

Причина, по которой обход Inorder работает так хорошо, заключается в том, что он всегда печатает сначала самый левый материал, затем корень, затем самый правый материал. Как мы хотим, чтобы наши поддеревья были такими: все слева от корня печатается слева от корня, все справа печатается справа. Обход Inorder, естественно, учитывает эту связь, и, поскольку мы печатаем строки и делаем отступы на основе nodeData, нам не нужно беспокоиться о длине наших данных. Узел может иметь длину 20 символов, и это не повлияет на алгоритм (хотя вы можете начать исчерпывать фактическое пространство экрана). Алгоритм не создает никакого промежутка между узлами, но это может быть легко реализовано, важно то, что они не перекрываются.

Просто чтобы доказать это для вас (не поверьте мне на слово) вот пример с довольно длинными персонажами

enter image description here

Как вы можете видеть, он просто настраивается в зависимости от размера данных, без перекрытия! Пока ваш экран достаточно большой. Если кто-нибудь когда-нибудь придумает простой способ напечатать одну строчку в консоли java (я весь слух), то это станет намного проще, достаточно легко для почти любого, кто имеет базовые знания о деревьях, чтобы понять и использовать, и лучшую часть нет риска плохого дублирования ошибок.

Ответ 11

Из вашего корня, подсчитайте количество ваших левых детей. Из общего числа оставшихся детей продолжайте печатать корень с отступом числа левых детей. Перейдите на следующий уровень дерева с уменьшенным числом отступов для левого ребенка, за которым следуют начальные два отступа для правильного ребенка. Уменьшите отступ левого ребенка на основе его уровня и его родителя с двойным отступлением для его правого брата.

Ответ 12

Для массива я нахожу это гораздо более кратким. Просто передайте массив. Может быть улучшено для обработки очень больших чисел (длинная длина цифр). Скопируйте и вставьте для С++:)

#include <math.h>
using namespace std;   
void printSpace(int count){
    for (int x = 0; x<count; x++) {
        cout<<"-";
    }
}
void printHeap(int heap[], int size){
    cout<<endl;
    int height = ceil(log(size)+1); //+1 handle the last leaves
    int width = pow(2, height)*height;
    int index = 0;
    for (int x = 0; x <= height; x++) { //for each level of the tree
        for (int z = 0; z < pow(2, x); z++) { // for each node on that tree level
            int digitWidth = 1;
            if(heap[index] != 0) digitWidth = floor(log10(abs(heap[index]))) + 1;
            printSpace(width/(pow(2,x))-digitWidth);
            if(index<size)cout<<heap[index++];
            else cout<<"-";
            printSpace(width/(pow(2,x)));
        }
        cout<<endl;
    }
}

Ответ 13

Вот предзаказная процедура, которая печатает общий древовидный график компактным образом:

        void preOrder(Node* nd, bool newLine=false,int indent=0)
        {
                if(nd != NULL) {    
                        if (newLine && indent) {
                                std::cout << "\n" << std::setw(indent) << ' '
                        }  else if(newLine)
                                std::cout << "\n";
                        cout<< nd->_c;
                        vector<Node *> &edges=nd->getEdges();
                        int eSize=edges.size();
                        bool nwLine=false;
                        for(int i=0; i<eSize; i++) {
                                preOrder(edges[i],nwLine,indent+1);
                                nwLine=true;
                        }
                }
        }

int printGraph()
{
     preOrder(root,true);
}

Ответ 14

У меня есть более простой код.......... рассмотрим дерево, состоящее из узлов структуры

 struct treeNode{
  treeNode *lc;
  element data;
  short int bf;
  treeNode *rc;
};

Глубина дерева можно найти с помощью

int depth(treeNode *p){
    if(p==NULL) return 0;
    int l=depth(p->lc);
    int r=depth(p->rc);
    if(l>=r)
        return l+1;
    else
        return r+1;
}

ниже функция gotoxy перемещает курсор в нужную позицию

void gotoxy(int x,int y)
{
printf("%c[%d;%df",0x1B,y,x);
}

Затем печать дерева может быть выполнена как:

void displayTreeUpDown(treeNode * root,int x,int y,int px=0){
if(root==NULL) return;
gotoxy(x,y);
int a=abs(px-x)/2;
cout<<root->data.key;
displayTreeUpDown(root->lc,x-a,y+1,x);
displayTreeUpDown(root->rc,x+a,y+1,x);
}

который можно вызвать с помощью:

display(t,pow(2,depth(t)),1,1);

Ответ 15

Вот мой код. Он печатает очень хорошо, возможно, не совсем симметрично. небольшое описание:

  • 1-я функция - печатает уровень по уровню (root lv → lv)
  • 2-я функция - расстояние от начала новой строки
  • 3-я функция - печатает узлы и вычисляет расстояние между двумя отпечатками;

void Tree::TREEPRINT()
{
    int i = 0;
    while (i <= treeHeight(getroot())){
        printlv(i);
        i++;
        cout << endl;
    }
}

void Tree::printlv(int n){
    Node* temp = getroot();
    int val = pow(2, treeHeight(root) -n+2);
    cout << setw(val) << "";
    prinlv(temp, n, val);
}

void Tree::dispLV(Node*p, int lv, int d)
{
    int disp = 2 * d;
    if (lv == 0){
        if (p == NULL){

            cout << " x ";
            cout << setw(disp -3) << "";
            return;
        }
        else{
            int result = ((p->key <= 1) ? 1 : log10(p->key) + 1);
            cout << " " << p->key << " ";
            cout << setw(disp - result-2) << "";
        }
    }
    else
    {
        if (p == NULL&& lv >= 1){
            dispLV(NULL, lv - 1, d);
            dispLV(NULL, lv - 1, d);
        }
        else{
            dispLV(p->left, lv - 1, d);
            dispLV(p->right, lv - 1, d);
        }
    }
}   

Input:

50-28-19-30-29-17-42-200-160-170-180-240-44-26-27

Выход: https://i.stack.imgur.com/TtPXY.png

Ответ 16

  1. Вам нужно будет выровнять порядок обхода вашего дерева.
  2. Выберите длину узла и длину пространства.
  3. Получите базовую ширину дерева относительно каждого уровня, которая равна node_length * nodes_count + space_length * spaces_count*.
  4. Найти связь между разветвлением, интервалом, отступом и расчетной шириной основания.

Код на GitHub: YoussefRaafatNasry/bst-ascii-визуализация

                                             07                     
                                             /\                     
                                            /  \                    
                                           /    \                   
                                          /      \                  
                                         /        \                 
                                        /          \                
                                       /            \               
                                      /              \              
                                     /                \             
                                    /                  \            
                                   /                    \           
                                 03                      11         
                                 /\                      /\         
                                /  \                    /  \        
                               /    \                  /    \       
                              /      \                /      \      
                             /        \              /        \     
                           01          05          09          13   
                           /\          /\          /\          /\   
                          /  \        /  \        /  \        /  \  
                        00    02    04    06    08    10    12    14