设计思路
我希望打印一棵二叉树是美观与最大程度节省空间的,所以就有了以下目标:
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);
}