最近在看一些数据结构和算法,对二叉树接触比较多,写程序时要想知道树的状态只能通过监视窗口翻着看,比较费事而且不清晰,因此想写个将二叉树可视化的程序,不过笔者水平有限,暂时只实现了针对完全二叉树的,如果不是完全二叉树则可以用0或其他特征字符代替前部缺省的字符,以下分享给大家取用。
贴代码之前先放个原理图,应该也是大家写相关程序主要要解决的问题:树同一层节点数据的间隔长度:
以下程序在visual stadio2017通过c++程序进行开发:
void showtree(T* arr,int len)
{
if (len == 1) //如果这个树总长为1直接输出
{
cout << arr[0] << endl; return;
}
if (len == 0)return; //如果这个树总长为0,直接返回
int* gap=new int[len+1](); //存储树相邻节点距离
int* pos = new int[len + 1];
int* alen = new int[len+1]; //存储节点字符长度
int cnt,tmp,wid=1,dep=log2(len);
int dtmp = dep-1;
int i;
for ( i= 0; i < len; i++)
{
alen[i]= (ostringstream() << arr[i]).str().length();
//提取树每个节点的字符长度
}
alen[len] = 1;
for (int j = len / 2; j < len; j++)
{
gap[j] = 1;
//没有子节点的各节点间隔设为1
}
gap[len] = 1; //末尾再设置个为1的间隔
int j = pow(2, dtmp) - 1; //倒数第二行的起点
tmp = (alen[2 * j + 1] + gap[2 * j + 2] + alen[2 * j + 2] - alen[j]) / 2;
gap[j] = gap[2 * j + 1] + tmp+1;
//该点(左侧起点)前部间隔为...
//2*j+1代表左子节点,2*j+2代表右子节点
for (j++; j < len/2; j++)
{
gap[j] = tmp+gap[2*j+1];
tmp= (alen[2 * j + 1] + gap[2 * j + 2] + alen[2 * j + 2] - alen[j]) / 2;
gap[j] += tmp;
//非左侧起点的其他点前部间隔计算公式
}
dtmp--;
//以下为其余各层的前部间隔计算公式
for (i = dtmp; i >= 0; i--)
{
int j = pow(2, dtmp) - 1;
tmp = (alen[2 * j + 1] + gap[2 * j + 2] + alen[2 * j + 2] - alen[j]) / 2;
//根据子节点中心得出该节点位置
gap[j] = gap[2 * j + 1] + tmp+1;
//加上起点位置,再向右偏移一格
for (j++; j < pow(2, dtmp+1) - 1; j++)
{
gap[j] = tmp + gap[2 * j + 1];
tmp = (alen[2 * j + 1] + gap[2 * j + 2] + alen[2 * j + 2] - alen[j]) / 2;
gap[j] += tmp;
//除起点外,其他要加上前后两部分的位置偏差
}
dtmp--; //每次层数减一(向上)
}
int newdep = 1;
for (cnt = 0; cnt < gap[0]; cnt++)cout << " ";
cout << arr[0] << endl; //将第一行打印出来,上方无斜杠,因此与以下循环分离
while (newdep < dep)
{
i = pow(2, newdep) - 1; //各行的第一位
for (cnt = 0; cnt < gap[i] + alen[i] / 2; cnt++)cout << " "; //打印前部间隔+半字符长度的空格
cout << '/'; //最左侧公式不同,因此分离
for (i++; i < pow(2, newdep + 1) - 1; i++)
{
//同理求出各斜杠位置,-1为斜杠自身占位符
for (cnt = 0; cnt < gap[i] + (alen[i - 1] + alen[i]) / 2-1; cnt++)cout << " ";
cout << '\\';
if (++i == pow(2, newdep + 1) - 1)break;
for (cnt = 0; cnt < gap[i] + (alen[i - 1] + alen[i]) / 2-1; cnt++)cout << " ";
cout << '/';
}
cout << endl; //每处理完一行则换行
//打印字符
for (i = pow(2, newdep) - 1; i < pow(2, newdep + 1) - 1; i++)
{
for (cnt = 0; cnt < gap[i]; cnt++)cout << " ";
cout << arr[i];
}
cout << endl;
newdep++;
}
//最后一层单独处理
i = pow(2, newdep) - 1;
for (cnt = 0; cnt < gap[i] + alen[i] / 2; cnt++)cout << " ";
cout << '/';
for (i++; i < len; i++)
{
for (cnt = 0; cnt < gap[i] + (alen[i - 1] + alen[i]) / 2 - 1; cnt++)cout << " ";
cout << '\\';
if (++i == pow(2, newdep + 1) - 1)break;
for (cnt = 0; cnt < gap[i] + (alen[i - 1] + alen[i]) / 2 - 1; cnt++)cout << " ";
cout << '/';
}
cout << endl;
for (i = pow(2, newdep) - 1; i < len; i++)
{
for (cnt = 0; cnt < gap[i]; cnt++)cout << " ";
cout << arr[i];
}
cout << endl;
delete[]gap;
delete[]pos;
delete[]alen; //最后还要记得内存回收
}
int main()
{
double arr[10] = { 8.5,54.1,4.8,55.8,56485,15.789,123.55,78.885,1,2.551};
showtree(arr, 10);
system("pause");
}
里边注释比较多久不多介绍了,而笔者这里做的输入接口是二叉树的层序遍历,因此二叉树结构体需要先做层序遍历,这个有很多的例程,这里也提供一个:
vector<int> levelOrder(TreeNode* root) {
vector<int> res;
if (!root)return res;
TreeNode* temp;
queue<TreeNode*> myq; //层序遍历通过队列实现
myq.push(root);
int len;
while (!myq.empty())
{
len = myq.size();
for (int i = 0; i <len ; i++) //每次遍历一层
{
temp = myq.front(); //取出元素放入,再将子节点入队
res.push_back(temp->val);
myq.pop();
if (temp->left)myq.push(temp->left);
if (temp->right)myq.push(temp->right);
}
}
return res;
}
这里采用的返回值是vector容器,由于其迭代器就是指针,因此可以很容易将vector转为数组,例如:
vector<int> ans;
ans = newlevelOrder(tre);
int* a = &ans[0];
这是所完成的可视化效果演示: