二叉树打印

整体思路参考了:https://blog.csdn.net/copica/article/details/39291141

主要参考了这两部分:(这篇只看了个图和开头,没必要全看)

  1. 今天看《数据结构与算法分析:C语言描述》练习题4.33, 发现各个结点的横坐标只要按照中序遍历的顺序就可以确定了
  2. 层次遍历确定层数
  3. 打印出来的图形

算法思想:

  1. 层次遍历得到打印的顺序,并记录每个节点的深度,方便换行和处理
  2. 中序遍历,得到每个节点应该出现的位置(即横坐标),在下面的代码中,如果把node->order依次放入一个有序队列中,在同一行输出,你会得到一个中序优先遍历的序列,每两个Key之间用一个空格分隔;
  3. 打印的调整:

下面针对每行代码进行讲解

  1. 遍历层次遍历得到的结点数组(vector)
  2. 若深度与上一次的不同,说明需要换行,进行如下操作

打印换行

打印需要的“|”

      即从打印父节点时,之前存储的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");
}

结果如图:

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值