Print binary tree in a pretty way using c++

前端 未结 4 1493
广开言路
广开言路 2021-02-01 09:03

I am a \"bit\" lost trying to print a binary tree like below in c++:

            8
           / \\
          /   \\
         /     \\
        5       10
       /         


        
4条回答
  •  感动是毒
    2021-02-01 09:58

    Here is an example of code creating a text-based representation of a binary tree. This demonstration uses a minimally useful binary tree class (BinTree), with a small footprint, just to avoid bloating the example's size.

    Its text-rendering member functions are more serious, using iteration rather than recursion, as found in other parts of the class.

    This does its job in three steps, first a vector of rows of string values is put together.

    Then this is used to format lines of text strings representing the tree.

    Then the strings are cleaned up and dumped to cout.

    As an added bonus, the demo includes a "random tree" feature, for hours of nonstop entertainment.

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using std::vector;
    using std::string;
    using std::cout;
    
    template 
    class BinTree {
        struct Node {
            T value;
            Node *left,*right;
            Node() : left(nullptr),right(nullptr) {}
            Node(const T& value) :value(value),left(nullptr),right(nullptr) {}
            // stack-abusing recursion everywhere, for small code
            ~Node() { delete left; delete right; }
            int max_depth() const {
                const int left_depth = left ? left->max_depth() : 0;
                const int right_depth = right ? right->max_depth() : 0;
                return (left_depth > right_depth ? left_depth : right_depth) + 1;
            }
        };
    
        Node *root;
    
    public:
        BinTree() : root(nullptr) {}
        ~BinTree() { delete root; }
    
        int get_max_depth() const { return root ? root->max_depth() : 0; }
        void clear() { delete root; root = nullptr; }
        void insert() {}
        template 
        void insert(const T& value, Args...more) {
            if(!root) {
                root = new Node(value);
            } else {
                Node* p = root;
                for(;;) {
                    if(value == p->value) return;
                    Node* &pchild = value < p->value ? p->left : p->right;
                    if(!pchild) { 
                        pchild = new Node(value);
                        break;
                    }
                    p = pchild;
                }
            }
            insert(more...);
        }
    
        struct cell_display {
            string   valstr;
            bool     present;
            cell_display() : present(false) {}
            cell_display(std::string valstr) : valstr(valstr), present(true) {}
        };
    
        using display_rows = vector< vector< cell_display > >;
    
        // The text tree generation code below is all iterative, to avoid stack faults.
    
        // get_row_display builds a vector of vectors of cell_display structs
        // each vector of cell_display structs represents one row, starting at the root
        display_rows get_row_display() const {
            // start off by traversing the tree to
            // build a vector of vectors of Node pointers
            vector traversal_stack;
            vector< std::vector > rows;
            if(!root) return display_rows();
    
            Node *p = root;
            const int max_depth = root->max_depth();
            rows.resize(max_depth);
            int depth = 0;
            for(;;) {
                // Max-depth Nodes are always a leaf or null
                // This special case blocks deeper traversal
                if(depth == max_depth-1) {
                    rows[depth].push_back(p);
                    if(depth == 0) break;
                    --depth;
                    continue;
                }
    
                // First visit to node?  Go to left child.
                if(traversal_stack.size() == depth) {
                    rows[depth].push_back(p);
                    traversal_stack.push_back(p);
                    if(p) p = p->left;
                    ++depth;
                    continue;
                }
    
                // Odd child count? Go to right child.
                if(rows[depth+1].size() % 2) {
                    p = traversal_stack.back();
                    if(p) p = p->right;
                    ++depth;
                    continue;
                }
    
                // Time to leave if we get here
    
                // Exit loop if this is the root
                if(depth == 0) break;
    
                traversal_stack.pop_back();
                p = traversal_stack.back();
                --depth;
            }
    
            // Use rows of Node pointers to populate rows of cell_display structs.
            // All possible slots in the tree get a cell_display struct,
            // so if there is no actual Node at a struct's location,
            // its boolean "present" field is set to false.
            // The struct also contains a string representation of
            // its Node's value, created using a std::stringstream object.
            display_rows rows_disp;
            std::stringstream ss;
            for(const auto& row : rows) {
                rows_disp.emplace_back();
                for(Node* pn : row) {
                    if(pn) {
                        ss << pn->value;
                        rows_disp.back().push_back(cell_display(ss.str()));
                        ss = std::stringstream();
                    } else {
                        rows_disp.back().push_back(cell_display());
            }   }   }
            return rows_disp;
        }
    
        // row_formatter takes the vector of rows of cell_display structs 
        // generated by get_row_display and formats it into a test representation
        // as a vector of strings
        vector row_formatter(const display_rows& rows_disp) const {
            using s_t = string::size_type;
    
            // First find the maximum value string length and put it in cell_width
            s_t cell_width = 0;
            for(const auto& row_disp : rows_disp) {
                for(const auto& cd : row_disp) {
                    if(cd.present && cd.valstr.length() > cell_width) {
                        cell_width = cd.valstr.length();
            }   }   }
    
            // make sure the cell_width is an odd number
            if(cell_width % 2 == 0) ++cell_width;
    
            // formatted_rows will hold the results
            vector formatted_rows;
    
            // some of these counting variables are related,
            // so its should be possible to eliminate some of them.
            s_t row_count = rows_disp.size();
    
            // this row's element count, a power of two
            s_t row_elem_count = 1 << (row_count-1);
    
            // left_pad holds the number of space charactes at the beginning of the bottom row
            s_t left_pad = 0;
    
            // Work from the level of maximum depth, up to the root
            // ("formatted_rows" will need to be reversed when done) 
            for(s_t r=0; r& rows) {
            if(!rows.size()) return;
            auto min_space = rows.front().length();
            for(const auto& row : rows) {
                auto i = row.find_first_not_of(' ');
                if(i==string::npos) i = row.length();
                if(i == 0) return;
                if(i < min_space) min_space = i;
            }
            for(auto& row : rows) {
                row.erase(0, min_space);
        }   }
    
        // Dumps a representation of the tree to cout
        void Dump() const {
            const int d = get_max_depth();
    
            // If this tree is empty, tell someone
            if(d == 0) {
                cout << " \n";
                return;
            }
    
            // This tree is not empty, so get a list of node values...
            const auto rows_disp = get_row_display();
            // then format these into a text representation...
            auto formatted_rows = row_formatter(rows_disp);
            // then trim excess space characters from the left sides of the text...
            trim_rows_left(formatted_rows);
            // then dump the text to cout.
            for(const auto& row : formatted_rows) {
                std::cout << ' ' << row << '\n';
            }
        }
    };
    
    
    int main() {
        BinTree bt;
    
        // Build OP's tree
        bt.insert(8,5,2,6,10,9,11);
        cout << "Tree from OP:\n\n";
        bt.Dump();
        cout << "\n\n";
    
        bt.clear();
    
        // Build a random tree 
        // This toy tree can't balance, so random
        // trees often look more like linked lists.
        // Just keep trying until a nice one shows up.
        std::random_device rd;
        std::mt19937 rng(rd());
    
        int MaxCount=20;
        int MaxDepth=5;
        const int Min=0, Max=1000;
    
        std::uniform_int_distribution dist(Min,Max);
    
        while(MaxCount--) {
            bt.insert(dist(rng));
            if(bt.get_max_depth() >= MaxDepth) break;
        }
    
        cout << "Randomly generated tree:\n\n";
        bt.Dump();
    }
    

    An example of the output:

    Tree from OP:
    
           8
          / \
         /   \
        /     \
       5      10
      / \     / \
     2   6   9  11
    
    
    Randomly generated tree:
    
                            703
                            / \
                           /   \
                          /     \
                         /       \
                        /         \
                       /           \
                      /             \
                     /               \
                    /                 \
                   /                   \
                  /                     \
                 /                       \
                /                         \
               /                           \
              /                             \
            137                             965
            / \                             /
           /   \                           /
          /     \                         /
         /       \                       /
        /         \                     /
       /           \                   /
      /             \                 /
     41             387             786
      \             / \             / \
       \           /   \           /   \
        \         /     \         /     \
        95      382     630     726     813
                                          \
                                          841
    

提交回复
热议问题