直观打印二叉树(DOS界面最完美的解决方案)

设计思路

我希望打印一棵二叉树是美观与最大程度节省空间的,所以就有了以下目标:
1.每一个父亲都应位于两孩子的中央,即孩子字符串的中心位置应在父亲字符串[-1]和[size]位置
2.打印出来的树宽度不应与高度呈指数关系,在值不相互重叠的前提下,树应越窄越好。

怎么实现上面两个小目标?

最难的地方在哪?答案是:如何确定每个节点的位置,使他们既不会相互干扰,又尽可能靠近。笔者认为这个问题如果用数学解决会非常困难,所以只能通过算法解决,最终确立了这样一个思路:层级遍历,从根节点开始,由左到右给这棵树增加节点,用一个conflictsolving函数解决插入每一个节点所带来的冲突问题。只要有了最终每个节点的位置pos,又知道每个节点的字符串长度,打印它并非难事。
再来考虑数据结构,在确定pos过程中,我们可能需要频繁访问一个节点的父节点、左邻居、右邻居,所以单用tree类是繁琐的,所以我们可以定义一个showtree类,利用tree类树初始化一个showtree类的树,再作进一步计算。

代码

#include <iostream>
#include<string>
#include<vector>
#include<queue>
#include<stack>
#include<cstdio>
using namespace std;

class tree_string {
public:
    string value;
    tree_string* left;
    tree_string* right;
    tree_string(string s = "",
        tree_string* left = nullptr,
        tree_string* right = nullptr) :value(s), left(left), right(right) {}
};

class tree_int {
public:
    int value;
    tree_int* left;
    tree_int* right;
    tree_int(int n = 0,
        tree_int* left = nullptr,
        tree_int* right = nullptr) :value(n), left(left), right(right) {}
};

class showtree {
public:
    showtree* father;
    showtree* left;
    showtree* right;
    int LorR;//left=-1;right=1;root=0;
    string value;
    int halflength;
    int pos;
    showtree* pre;
    showtree* next;
    showtree(showtree* father = nullptr,
        int LorR = 0,
        string v = "",
        showtree* pre = nullptr) : father(father), LorR(LorR), pre(pre) {
        value = v;
        //value如果是一个半角数字字符的string,转化成全角(这样单个数字能够对齐)
        vector<string> semi{ "1","2","3","4","5","6","7","8","9","0" };
        vector<string> full{ "1","2", "3", "4", "5", "6", "7", "8", "9", "0" };
        for (int i = 0; i < 10; i++) {
            if (value == semi[i])value = full[i];
        }
        //value如果是奇数个字符,给它加一个空格,凑成偶数。后面代码中的value.size()+1
        //都可以省去+1,留着是因为初步改动时没想到更改value而留下的
        if (value.size() % 2)value += " ";
        halflength = value.size() + 1 >> 2;//相当于一边有几个单字符
        pos = father ? father->pos + LorR * (1 + father->halflength) : 0;
        left = nullptr;
        right = nullptr;
        next = nullptr;
    }
};
void showtostk(showtree* anc, stack<showtree*>& stkshow) {//把以anc为根的树进栈,anc不进
    int precount = 1, count = 0;
    queue<showtree*> qtemp;
    qtemp.push(anc);
    while (precount) {
        if (qtemp.front()->left) {
            qtemp.push(qtemp.front()->left);
            count++;
        }
        if (qtemp.front()->right) {
            qtemp.push(qtemp.front()->right);
            count++;
        }
        if (qtemp.front() != anc)
            stkshow.push(qtemp.front());
        qtemp.pop();
        precount--;
        if (precount == 0) {
            precount = count;
            count = 0;
        }
    }
}
void movesubtree(showtree* anc, int confn) {
    queue<showtree*>qtemp;
    int precount = 1, count = 0;//移左树
    qtemp.push(anc->left);
    showtree* top;
    while (precount) {
        top = qtemp.front();
        top->pos -= confn;
        if (top->left) {
            qtemp.push(top->left);
            count++;
        }
        if (top->right) {
            qtemp.push(top->right);
            count++;
        }
        qtemp.pop();
        precount--;
        if (precount == 0) {
            precount = count;
            count = 0;
        }
    }
    precount = 1;//移右树
    qtemp.push(anc->right);
    while (precount) {
        top = qtemp.front();
        top->pos += confn;
        if (top->left) {
            qtemp.push(top->left);
            count++;
        }
        if (top->right) {
            qtemp.push(top->right);
            count++;
        }
        qtemp.pop();
        precount--;
        if (precount == 0) {
            precount = count;
            count = 0;
        }
    }
}
showtree* conflictsolving(showtree* curshow, showtree* PorN) {
    if (PorN == nullptr)return nullptr;
    //获取与邻居的冲突值
    int p1 = PorN->pos, p2 = curshow->pos;
    int hl1 = PorN->halflength, hl2 = curshow->halflength;
    int confn = hl1 + hl2 + 2 - (p2 - p1);
    if (confn <= 0)return nullptr;//没冲突
    confn = (confn + 1) >> 1;//将冲突量设为左右子树需要进行的偏移量
    //有冲突,则找最小公共祖先
    showtree* anc1 = PorN->father, * anc2 = curshow->father;
    while (anc1 != anc2) {
        anc1 = anc1->father;
        anc2 = anc2->father;
    }
    //LCA就是anc1;
    movesubtree(anc1, confn);
    return anc1;
}
void pritree(tree_string* root) {
    if (root == nullptr)return;
    queue<tree_string*>qtree;
    queue<showtree*>qshow;
    qtree.push(root);
    showtree* rootshow = new showtree(nullptr, 0, root->value, nullptr);
    qshow.push(rootshow);

    int precount = 1;//上一层qtree中待用于初始化showtree的元素个数
    int count = 0;//队列中包含下一层的节点个数

    showtree* preshow = nullptr;//记录同层的上一个showtree,用于初始化preshow的next以及curshow的pre
    showtree* curshow = nullptr;//当前初始化showtree

    while (precount) {//将tree初始化为showtree
        if (qtree.front()->left) {//将当前qtree的tree节点的孩子用于生成showtree的孩子
            curshow = new showtree(qshow.front(), -1, qtree.front()->left->value, preshow);
            qshow.front()->left = curshow;//父亲认养
            if (preshow)preshow->next = curshow;
            bool isconf = conflictsolving(curshow, preshow) ? true : false;//有冲突吗?
            while (isconf) {//只要调整过程有冲突,全树进栈,重头再来
                isconf = false;
                stack<showtree*> stkconf;
                showtostk(rootshow, stkconf);
                while (!stkconf.empty()) {
                    if (conflictsolving(stkconf.top(), stkconf.top()->pre))
                        isconf = true;
                    stkconf.pop();
                }
            }
            preshow = curshow;
            //qtree孩子入栈
            qtree.push(qtree.front()->left);
            qshow.push(curshow);
            count++;
        }
        if (qtree.front()->right) {
            curshow = new showtree(qshow.front(), 1, qtree.front()->right->value, preshow);
            qshow.front()->right = curshow;
            if (preshow)preshow->next = curshow;
            if (conflictsolving(curshow, preshow)) {//有冲突,从右往左,逐层向上解决冲突
                stack<showtree*> stkconf;
                showtostk(rootshow, stkconf);
                while (!stkconf.empty()) {
                    conflictsolving(stkconf.top(), stkconf.top()->pre);
                    stkconf.pop();
                }
            }
            preshow = curshow;
            qtree.push(qtree.front()->right);
            qshow.push(curshow);
            count++;
        }
        qtree.pop();
        qshow.pop();
        precount--;
        if (precount == 0 && count != 0) {//结束当前层,还有下一层
            precount = count;
            count = 0;
            preshow = nullptr;
        }
    }

    //showtree初始化完毕,统计最左最右坐标
    int maxpos = rootshow->halflength, minpos = -maxpos;//左右边界现坐标
    qshow.push(rootshow);
    precount = 1;
    while (precount) {
        showtree* qf = qshow.front();
        if (qf->left == nullptr) {//只有左孩子为空才有可能是最左的
            minpos = qf->pos - (qf->halflength) < minpos ?
                qf->pos - (qf->halflength) :
                minpos;
        }
        else {//左孩子不为空就push进来
            qshow.push(qf->left);
            count++;
        }
        if (qf->right == nullptr) {
            maxpos = qf->pos + (qf->halflength) > maxpos ?
                qf->pos + (qf->halflength) :
                maxpos;
        }
        else {
            qshow.push(qf->right);
            count++;
        }
        qshow.pop();
        precount--;
        if (precount == 0) {
            precount = count;
            count = 0;
        }
    }
    //将每个pos加(-minpos),同时打印
    vector<string>vspre(maxpos - minpos + 1, " ");//上一层的缓存
    vector<string>vscur(maxpos - minpos + 1, " ");//本层的缓存
    vector<string>vsnext(maxpos - minpos + 1, " ");//下一层的缓存
    qshow.push(rootshow);
    precount = 1;//当前层剩余待处理节点
    while (precount) {
        showtree* qf = qshow.front();
        qf->pos -= minpos;
        //每逢节点,渲染三层
        if (qf->father) {  //第一层无上层
            vspre[qf->pos] = qf->LorR == -1 ? "┌ " : "┐ ";//带帽┏┓
            if (qf->pos < qf->father->pos) {//是左孩子
                for (int i = qf->pos + 1; i < qf->father->pos; i++) {
                    vspre[i] = "─ ";//━
                }
            }
            else {//是右孩子
                for (int i = qf->father->pos + 1; i < qf->pos; i++) {
                    vspre[i] = "─ ";
                }
            }
        }
        //渲染本层,计算偏移量。小设计:偶数字符时,左孩子优先填左,右孩子优先填右
        int start = qf->pos - (qf->value.size() >> 2)+ //优先填左
            (qf->LorR == 1 && (qf->value.size() + 1 >> 1) % 2 == 0 ? 1 : 0)+//偶数右孩子+1填右
            (qf->LorR != 1 && qf->value.size() == 4 && qf->value.back()==' ' ? 1 : 0);
        auto its = qf->value.begin();
        auto itv = vscur.begin() + start;
        while (its != qf->value.end()) {
            string temp;
            temp += *its;
            its++;
            if (its != qf->value.end()) {
                temp += *its;
                its++;
            }
            *itv = temp;
            itv++;
        }
        //渲染下层
        vsnext[qf->pos] = qf->left && qf->right ? "┴ " ://┻
            (qf->left && !qf->right ? "┘ " ://┛
                !qf->left && qf->right ? "└ " : " ");//┗
        if (qf->left) {
            qshow.push(qf->left);
            count++;
        }
        if (qf->right) {
            qshow.push(qf->right);
            count++;
        }
        qshow.pop();
        precount--;
        if (precount == 0) {//该层结束
            precount = count;
            count = 0;

            auto itpre = vspre.begin(), itnext = vsnext.begin();
            while (itpre != vspre.end()) {//打印上层,打一个,用vsnext初始化一个
                cout << *itpre;//打印上层该元素
                *itpre = *itnext;//用下层去初始化上层
                *itnext = " ";//下层清空
                itpre++;
                itnext++;
            }
            cout << endl;
            //打印本层并清空缓存
            for (auto& s : vscur) {
                cout << s;
                s = " ";
            }
            cout << endl;
        }
    }
}
tree_string* IntToString(tree_int* root) {
    if (root == nullptr)
        return nullptr;
    //NumToString
    tree_string* resp = new tree_string;
    int n = root->value;
    string s;
    if (n == 0) {
        s = "0";
    }
    else {
        stack<int>num;
        s = "";
        if (n > 0) {
            while (n) {
                num.push(n % 10);
                n /= 10;
            }
        }
        else {
            s += "-";
            while (n) {
                num.push(-(n % 10));
                n /= 10;
            }
        }
        while (!num.empty()) {
            s += '0' + num.top();
            num.pop();
        }
    }
    resp->value = s;
    resp->left = IntToString(root->left);
    resp->right =IntToString(root->right);
    return resp;
}
void pritree(tree_int* root) {
    pritree(IntToString(root));
}

int main()
{
    tree_string root("1");
    root.left = new tree_string("2");
    root.right = new tree_string("2");

    tree_string root1("3");
    root1.left = 0;
    root1.right = 0;

    tree_string root2("444");
    root2.left = new tree_string("4");
    root2.right = new tree_string("4");

    tree_string root3("5");
    root3.left = 0;
    root3.right = 0;

    tree_string root4("6666");
    root4.left = 0;
    root4.right = 0;

    root.left->left = &root1;
    root.left->right = &root2;
    root.right->left = &root3;
    root.right->right = &root4;

    pritree(&root);
}

运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值