整体思路参考了:https://blog.csdn.net/copica/article/details/39291141
主要参考了这两部分:(这篇只看了个图和开头,没必要全看)
- 今天看《数据结构与算法分析:C语言描述》练习题4.33, 发现各个结点的横坐标只要按照中序遍历的顺序就可以确定了
- 层次遍历确定层数
- 打印出来的图形
算法思想:
- 层次遍历得到打印的顺序,并记录每个节点的深度,方便换行和处理
- 中序遍历,得到每个节点应该出现的位置(即横坐标),在下面的代码中,如果把node->order依次放入一个有序队列中,在同一行输出,你会得到一个中序优先遍历的序列,每两个Key之间用一个空格分隔;
- 打印的调整:
下面针对每行代码进行讲解
- 遍历层次遍历得到的结点数组(vector)
- 若深度与上一次的不同,说明需要换行,进行如下操作
打印换行 打印需要的“|” 即从打印父节点时,之前存储的notePrint中(存储了|出现的位置),遍历需要打印的位置,即从开始到最后,如果当前位置在notePrint中出现,那么打印|,否则打印空格 打印换行 |
3.打印左边的结点需要的符号和空格
如果左边结点存在:
确定左边结点的order,保存这个位置到notePrint中
打印左边结点之前的空格
打印下一行的|对应这一行的空格
打印从现在这个位置到当前结点的order之间的“_”
不存在
打印从现在这个位置到当前结点order之间的空格
4.打印当前结点的Key,更新位置lastPrintSize
5.打印右边结点需要的符号(else部分不需要打印空格,见注释)
若存在:打印“_”,需要从当前位置打印到右节点的order+右节点Key的size-1
打印下一行|对应这一行的空格
保存下一行的|的位置到notePrint中
若不存在:什么也不需要做,因为左边的结点会自动找到应该在的位置(根据自己和左节点的order),所以不需要判断
好了,这就是所有,代码见下:
Tree.h(//改进,原本写的需要保存父节点Father,实际不需要,此处已经被注释掉,具体解释见代码)
#pragma once
#include<iostream>
#include<string>
#include<queue>
#include<vector>
using namespace std;
struct TreeNode {
TreeNode * left;
TreeNode * right;
string Key;
int depth;
int order;
TreeNode(string k) :Key(k) {
left = NULL;
right = NULL;
depth = -1;
order = -1;
}
};
class Tree {
TreeNode * root;
vector<TreeNode*> nums;
//vector<TreeNode*> numsFather;
public:
Tree(TreeNode * r) {
root = r;
}
TreeNode* getRoot() {
return root;
}
void Hierarchical_traversal() {
queue<TreeNode*> TreeQueue;
TreeQueue.push(root);
root->depth = 0;
nums.push_back(root);
//numsFather.push_back(NULL);
while (!TreeQueue.empty()) {
TreeNode* r = TreeQueue.front();
TreeQueue.pop();
if (r->left != NULL) {
TreeQueue.push(r->left);
r->left->depth = r->depth + 1;
nums.push_back(r->left);
//numsFather.push_back(r);
}
if (r->right != NULL) {
TreeQueue.push(r->right);
r->right->depth = r->depth + 1;
nums.push_back(r->right);
//numsFather.push_back(r);
}
}
}
void Middle_Traversal(TreeNode* node,int& printSize) {
if (node->left != NULL) {
Middle_Traversal(node->left, printSize);
}
node->order = printSize ;
printSize = node->order + node->Key.size() + 1 ;
if (node->right != NULL) {
Middle_Traversal(node->right, printSize);
}
}
void PrintTree() {
Hierarchical_traversal();
int printSize = 0;
Middle_Traversal(root, printSize);
/*FOR TEST
for each (TreeNode* node in nums) {
int num = node->order;
cout << node->order << endl;
PrintBlank(num);
cout << node->Key;
cout << endl;
}*/
vector<int> notePrint;
int currentDepth = 0;
int lastPrintSize = 0;
int pos = 0;
for each (TreeNode* node in nums)
{
if (node->depth > currentDepth) {
//换行,打印中间行
printf("\n");
int i = 0;
int j = 0;
int len = notePrint.size() ;
int totalPrintSize = notePrint[len-1];
for (; j <= totalPrintSize && i<len; j++) {
if (notePrint[i] == j) {
printf("|");
i++;
}
else {
printf(" ");
}
}
//换行
printf("\n");
lastPrintSize = 0;
notePrint.clear();
}
//打印空格和左节点的____横线
if (node->left != NULL) {
int leftorder = node->left->order;
notePrint.push_back(leftorder);
int num = leftorder - lastPrintSize ;
PrintBlank(num );
PrintBlank(1);//下一行的|的位置
num = node->order - leftorder;
PrintLine(num - 1);//减一是因为|在这一行对应一个空格
}
else {//打印空格
int num = node->order - lastPrintSize;
PrintBlank(num);
}
//打印该节点
cout << node->Key;
lastPrintSize = node->order + node->Key.size();
//打印右节点的____横线
if (node->right != NULL) {
int rightorder = node->right->order;
notePrint.push_back(rightorder + node->right->Key.size()- 1);//在最后一位的位置上打印|
int num = rightorder - lastPrintSize+node->right->Key.size();
PrintLine(num - 1);//1是后面的竖线,与Key的最后一位对齐
PrintBlank(1);//与下一行的|对应
lastPrintSize += num;
}
else {
/*
//原来的想法是,如果右边的结点不存在,尽管不需要打印任何符号,但是需要对lastPrintSize
//重新定位,定位到后面的结点可以开始的最早位置,即父节点打印过后的位置
//这样定位是因为node->order存储的是中序遍历下的位置,下一个结点肯定比父节点中序的位置靠后
//新:这里为什么不需要判断父节点存不存在?为什么不需要处理这种情况?、
//因为无论下一个结点是左节点还是右节点,都要为自己的左儿子做准备,准备左儿子的过程中
//(无论左儿子是否存在),都会自动打印左儿子(左儿子不存在,打印当前节点)前面的空格
//此处对应node->left!=NULL 的If和else
//这样就会自动实现定位
TreeNode * father = numsFather[pos];
if (father != NULL) {//父节点存在,即为根
int num = father->order + father->Key.size() - lastPrintSize;
if (num >= 0) {//说明当前结点是父节点的左儿子,需要为当前结点的右兄弟重新定位
//PrintBlank(num);
//lastPrintSize += num;
}
else {//是右儿子,不需要重新定位,因为下一个结点的打印依赖于自己的node->order
}
}
else {//父节点不存在,即为根,不处理,因为下一次会自动换行
}*/
}
//保存这次的最后位置,方便下次使用
currentDepth = node->depth;
pos++;
}
printf("\n");
}
static void PrintBlank(int num) {
for (int i = 0; i < num; i++) {
printf(" ");
}
}
static void PrintLine(int num) {
for (int i = 0; i < num; i++) {
printf("_");
}
}
};
主函数测试:
#include"Tree.h"
using namespace std;
int main() {
TreeNode* root = new TreeNode("10");
Tree* tree = new Tree(root);
root->left=new TreeNode("10");
root->right = new TreeNode("100");
root->right->left = new TreeNode("2");
root->left->left = new TreeNode("sys");
root->left->right = new TreeNode("pop");
root->right->right = new TreeNode("xyz");
root->left->right->left = new TreeNode("a");
root->right->left->right = new TreeNode("b");
root->left->left->left = new TreeNode("wcc");
root->right->right->right = new TreeNode("??");
root->right->right->right->right = new TreeNode("***");
tree->PrintTree();
system("pause");
}
结果如图: