可视化树的算法有很多,这里我们借助Graphviz实现二叉搜索树BST(二叉树)和B-Tree(多叉树)的快速可视化。关于一般的算法,可以到知网或者万方数据中去查阅,不在此处详谈。
基本的思想就是利用Gphviz可视化树的基本思想,将树转换为dot文件,然后利用Graphviz将其转换为图片。这种方法的特点就是,简单,快速,高效。这种方法可以用于辅助理解程序运行过程、调试程序。
请在使用前确认,你的系统已经正确安装了Graphviz,并配置了dot命令。
下面的程序依赖于命令:
dot example.dot -Tpng -o 1.png
完整的代码地址,可以在github BST和github BTree得到。
下面给出一些例子,文章末尾也摘取了c++实现的自动转换代码。
将转换的图片,链接成gif图片,即可得到运行时动态效果:
先序遍历:
广度优先遍历:
省略中间过程,创建主链为:
中间过程省略,则最终生成的平衡BST为:
Splay Tree例子此处省略。
注意下面的代码,给出了转换部分的实现;转换部分依赖于你的节点的定义,你可以根据需要调整。在你程序中合适地方插入toPng函数来生成图片。
定义文件:
/** * 二叉树搜索树结点类BSTNode * 暂时处理整型数据 */
class BSTNode {
public:
BSTNode(const int& e,BSTNode*p ,BSTNode *l=0,BSTNode *r=0):key(e),height(1) ,parent(p),
left(l),right(r){
}
std::string toString() const{ // 用于调试
std::ostringstream oss;
oss << key;
return oss.str();
}
private:
int key;
int height; // 以这个结点为根的树的高度
BSTNode *parent,*left,*right;
friend class BiTreePrinter; // 将BST转换为图片的类
};
/** * BST边类 */
struct BSTEdge {
BSTEdge(const std::string &f,const std::string &t,bool vis=true): from(f), to(t), isVisiable(vis) {
}
std::string toString() {
if(isVisiable)
return from+"->"+to;
else
return from+"->"+to+"[weight=100 style=invis]";
}
std::string from;
std::string to;
bool isVisiable;
};
/** * 利用绘制二叉树 * 1)将树形写入.dot文件 * 2)利用Graphviz程序转换.dot文件为png图片 * dot转换为图片阶段 系统必须安装有graphviz并配置有dot命令 否则失效 * 利用系统命令例如: system ("dot example.dot -Tpng -o 1.png");转换为图片 * 关于Graphviz更多内容参见:http://www.graphviz.org */
class BiTreePrinter {
public:
static void toPng(const BST *bst,const std::string &desp=std::string(),const BSTNode* pcur=0);
static std::string fontColor,fillColor,currentFillColor,currentFontColor,edgeColor,arrowheadType,width,height,fontsize; // 参数
static std::string prefix; // 文件名前缀
static long fileCounter; //文件编号
private:
static void addEdge(std::vector<std::string> &invisNodeVec,std::vector<BSTEdge> &edgeVec,const BSTNode* from);
static void writePng(std::vector<std::string> &invisNodeVec,std::vector<BSTEdge> &edgeVec,const std::string& desp,const BSTNode* pcur=0);
static std::string getNextFilename();
};
实现文件:
/* * treeprinter.cpp * * Created on: 2015年5月21日 * Author: wangdq */
#include <cstdlib> // std::system
#include <queue>
#include <iostream>
#include <fstream>
#include <sstream>
#include "treeprinter.h"
std::string BiTreePrinter::fontColor="black",BiTreePrinter::fillColor="#FFFFFF",
BiTreePrinter::currentFillColor="red",BiTreePrinter::width="0.5",
BiTreePrinter::height="0.5",BiTreePrinter::fontsize="16",BiTreePrinter::currentFontColor="black",
BiTreePrinter::edgeColor="blue",BiTreePrinter::arrowheadType="normal";
long BiTreePrinter::fileCounter = 1;
std::string BiTreePrinter::prefix="BST";
/** * 将绑定的BST转换为图片 * desp为描述字符串 * pcur指向当前节点 */
void BiTreePrinter::toPng(const BST *bst,const std::string &desp,const BSTNode* pcur)
{
if(bst == 0) {
std::cerr << "Printer not bind to any BST"<<std::endl;
return;
}
if(bst->isEmpty())
return;
std::vector<std::string> invisNodeVec;
std::vector<std::string> visNodeVec;
std::vector<BSTEdge> edgeVec;
std::queue<const BSTNode *> nodeQueue;
nodeQueue.push(bst->getRoot());
while(!nodeQueue.empty()) { // 广度优先遍历
const BSTNode * current = nodeQueue.front();
nodeQueue.pop();
if(current->left != 0)
nodeQueue.push(current->left);
if(current->right != 0)
nodeQueue.push(current->right);
if(current->left != 0 || current->right != 0)
addEdge(invisNodeVec,edgeVec,current);
}
writePng(invisNodeVec,edgeVec,desp,pcur);
}
/** * 添加结点from相关的边 */
void BiTreePrinter::addEdge(std::vector<std::string> &invisNodeVec,std::vector<BSTEdge> &edgeVec,const BSTNode* from){
std::string fromId = from->toString();
std::string virtualId=std::string("v")+fromId;
if(from->left == 0 && from->right == 0)
return;
invisNodeVec.push_back(virtualId);
if(from->left != 0 && from->right != 0) {
edgeVec.push_back(BSTEdge(fromId,from->left->toString()));
edgeVec.push_back(BSTEdge(fromId,virtualId,false));
edgeVec.push_back(BSTEdge(fromId,from->right->toString()));
}else if(from->left == 0) {
edgeVec.push_back(BSTEdge(fromId,virtualId,false));
edgeVec.push_back(BSTEdge(fromId,from->right->toString()));
}else {
edgeVec.push_back(BSTEdge(fromId,from->left->toString()));
edgeVec.push_back(BSTEdge(fromId,virtualId,false));
}
}
/** * 写入dot文件并转换为png * 可以根据需要修改此部分参数 */
void BiTreePrinter::writePng(std::vector<std::string> &invisNodeVec,std::vector<BSTEdge> &edgeVec,const std::string& desp,const BSTNode* pcur) {
long count = fileCounter;
std::string filename = getNextFilename();
std::ofstream stream(filename.c_str());
// print author and contact info
stream << "/************************************************" <<std::endl
<< "Auto generated by my program which transfer Binary Search Tree to dot file." << std::endl
<< "Author: wangdq " <<std::endl
<< "Time: 2015-05-30" <<std::endl
<< "CSDN: http://blog.csdn.net/wangdingqiaoit"<<std::endl
<< "************************************************/" <<std::endl<<std::endl;
// print description
stream << "digraph BST {" << std::endl;
stream << "\tlabel=\"(" << count <<")\t" << desp << "\";"<< "labelloc=b;labeljust=center;"<<std::endl;
// print settings
stream << "\tnodesep=0.35" << std::endl
<< "\tordering=out" << std::endl
<< "\tnode[width=" << width << ",height=" <<height << ",fontsize=" << fontsize << ",fixedsize=true,style=\"filled\", fillcolor=\""
<< fillColor << "\",fontcolor=\"" << fontColor << "\"];" << std::endl
<< "\tedge[color=\"" << edgeColor << "\", arrowhead=\"" << arrowheadType << "\"];"<< std::endl;
// print invisible node
stream << "\t/* invisible nodes*/" << std::endl;
stream << " \t{ node[style=invis]" << std::endl;
for(std::vector<std::string>::iterator it = invisNodeVec.begin(); it != invisNodeVec.end();++it)
stream << "\t\t" << *it << std::endl;
stream << "\t}" << std::endl;
//set current node color
if(pcur != 0) {
stream << "\t/* set current node color attributes*/" << std::endl;
stream <<"\t" << pcur->toString() << "[fillcolor=\"" << currentFillColor << "\",fontcolor=\"" << currentFontColor <<"\"];"<<std::endl;
}
//print edges
stream << "\t/* edges*/" << std::endl;
for(std::vector<BSTEdge>::iterator it = edgeVec.begin(); it != edgeVec.end();++it)
stream <<"\t" << (*it).toString() << std::endl;
stream << "}"<< std::endl;
stream.close();
//transfer from dot file to png picture
std::string cmd("dot -Tpng");
cmd += " "+filename+" -o"+filename+".png";
std::system(cmd.c_str());
std::cout << "tree saved in file: " << filename<< std::endl;
}
/** * 获取下一个文件名 */
std::string BiTreePrinter::getNextFilename() {
const std::string ext = ".dot";
std::string filename(prefix);
std::ostringstream oss;
oss << fileCounter++;
filename += oss.str();
filename += ext;
return filename;
}
转换的dot文件如下所示:
/************************************************ Auto generated by my program which transfer Binary Search Tree to dot file. Author: wangdq Time: 2015-05-30 CSDN: http://blog.csdn.net/wangdingqiaoit ************************************************/
digraph BST {
label="(23) search: 88";labelloc=b;labeljust=center;
nodesep=0.35
ordering=out
node[width=0.5,height=0.5,fontsize=16,fixedsize=true,style="filled", fillcolor="#FFFFFF",fontcolor="black"];
edge[color="blue", arrowhead="normal"];
/* invisible nodes*/
{ node[style=invis]
v66
v44
v99
v77
}
/* set current node color attributes*/
88[fillcolor="red",fontcolor="black"];
/* edges*/
66->44
66->v66[weight=100 style=invis]
66->99
44->v44[weight=100 style=invis]
44->55
99->77
99->v99[weight=100 style=invis]
77->v77[weight=100 style=invis]
77->88
}
B-Tree节点定义如下:
/** * M阶B-Tree结点 * 每个节点包含M-1个键值和M个指针 * 实际上每个结点多分配一个键值和指针用于辅助空间 */
template<typename T,int M> class BTree;
template<typename T,int M=5>
class BTreeNode {
public:
BTreeNode():parent(0),keynum(0),isLeaf(true) {
for(int i=0;i < M+1;++i)
childs[i] = 0;
}
BTreeNode(const T& k,BTreeNode *p=0) {
keys[0]=k;
parent = p;
keynum = 1;
isLeaf = true;
for(int i=0;i < M+1;++i)
childs[i] = 0;
}
std::string toString() const{ // 用于调试
std::stringstream ss;
ss << keys[0];
return ss.str();
}
private:
T keys[M]; // 键
BTreeNode *childs[M+1]; // 孩子指针
BTreeNode *parent; // 父节点指针
int keynum; // 键数目
bool isLeaf; // 是否是叶子结点
friend class BTree<T,M>;
friend class BTreePrinter; // 打印B-Tree为图片
};
实现如下:
/* * btreeprinter.h * * Created on: 2015年5月18日 * Author: wangdq */
#ifndef BTREEPRINTER_H_
#define BTREEPRINTER_H_
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include "btree.h"
/** * B-Tree边类 */
struct BTreeEdge {
BTreeEdge(const std::string &f,const std::string &t,int fIndex,int tIndex,bool isWest=true):
from(f), fromIndex(fIndex),toIndex(tIndex),to(t),west(isWest) {
}
std::string toString() const{
std::string dir = ":se";
std::stringstream ss;
ss << fromIndex;
std::string fIndex = ss.str();
ss << toIndex;
std::string tIndex = ss.str();
if(west)
dir = std::string(":sw");
std::stringstream oss;
oss << "\"" << from<< "\":f"<< fIndex<< dir<< "->"<< "\""<< to<< "\":f"<< toIndex;
return oss.str();
}
std::string from;
int fromIndex,toIndex;
std::string to;
bool west;
};
/** * 利用绘制B-Tree * 1)将树形写入.dot文件 * 2)利用Graphviz程序转换.dot文件为png图片 * dot转换为图片阶段 系统必须安装有graphviz并配置有dot命令 否则失效 * 利用系统命令例如: system ("dot example.dot -Tpng -o 1.png");转换为图片 * 关于Graphviz更多内容参见:http://www.graphviz.org */
class BTreePrinter {
public:
static std::string fontColor,fillColor,edgeColor,arrowheadType,width,height,fontsize; // 参数
static std::string prefix; // 文件名前缀
static long fileCounter; //文件编号
static std::string intToString(int i) {
std::ostringstream oss;
oss << i;
return oss.str();
}
/** * 将btree转换为图片 * desp为描述内容 */
template<typename T,int M>
static void toPng(const BTree<T,M>* btree,const std::string &desp=std::string()) {
if(btree == 0) {
std::cerr << "Printer not bind to any B-Tree"<<std::endl;
return;
}
if(btree->isEmpty())
return;
std::vector<std::string> visNodeVec;
std::vector<BTreeEdge> edgeVec;
std::queue<const BTreeNode<T,M>*> nodeQueue;
nodeQueue.push(btree->getRoot());
while(!nodeQueue.empty()) { // 广度优先遍历
const BTreeNode<T,M>* current = nodeQueue.front();
nodeQueue.pop();
if(!current->isLeaf)
for(int i=0;i <= current->keynum;++i)
nodeQueue.push(current->childs[i]);
addEdge(visNodeVec,edgeVec,current);
}
writePng(visNodeVec,edgeVec,desp);
}
private:
static std::string getNextFilename() {
const std::string ext = ".dot";
std::string filename(prefix);
std::ostringstream oss;
oss << fileCounter++;
filename += oss.str();
filename += ext;
return filename;
}
template<typename T,int M>
static void addEdge(std::vector<std::string> & visNodeVec,std::vector<BTreeEdge> &edgeVec,
const BTreeNode<T,M>* from) {
if(from->keynum < 0 ) return;
std::string label = from->toString()+"[label=\"";
for(int i=0;i < from->keynum;++i) {
label += std::string("<f")+intToString(i)+"> "+intToString(from->keys[i]);
if(i != from->keynum-1)
label += "|";
}
label+="\"];";
visNodeVec.push_back(label);
if(from->isLeaf) return;
for(int i=0;i < from->keynum;++i)
edgeVec.push_back(BTreeEdge(from->toString(),from->childs[i]->toString(),i,from->childs[i]->keynum-1,true));
edgeVec.push_back(BTreeEdge(from->toString(),from->childs[from->keynum]->toString(),
from->keynum-1,from->childs[from->keynum]->keynum /2,false));
}
static void writePng(std::vector<std::string> & visNodeVec,std::vector<BTreeEdge> &edgeVec
,const std::string& desp) {
long count = fileCounter;
std::string filename = getNextFilename();
std::ofstream stream(filename.c_str());
// print author and contact info
stream << "/************************************************" <<std::endl
<< "Auto generated by my program which transfer B-Tree to dot file." << std::endl
<< "Author: wangdq " <<std::endl
<< "Time: 2015-06-08" <<std::endl
<< "CSDN: http://blog.csdn.net/wangdingqiaoit"<<std::endl
<< "************************************************/" <<std::endl<<std::endl;
// print description
stream << "digraph BTree {" << std::endl;
stream << "\tlabel=\"(" << count <<")\t" << desp << "\";"<< "labelloc=b;labeljust=center;"<<std::endl;
// print settings
stream << "\tordering=out" << std::endl
<< "\tnode[shape=record,width=" << width << ",height=" <<height << ",fontsize=" << fontsize << ",style=\"filled\", fillcolor=\""
<< fillColor << "\",fontcolor=\"" << fontColor << "\"];" << std::endl
<< "\tedge[color=\"" << edgeColor << "\", arrowhead=\"" << arrowheadType << "\"];"<< std::endl;
//print visible node
for(std::vector<std::string>::iterator it = visNodeVec.begin(); it != visNodeVec.end();++it)
stream << "\t\t" << *it << std::endl;
//print edges
stream << "\t/* edges*/" << std::endl;
for(std::vector<BTreeEdge>::iterator it = edgeVec.begin(); it != edgeVec.end();++it)
stream <<"\t" << (*it).toString() << std::endl;
stream << "}"<< std::endl;
stream.close();
//transfer from dot file to png picture
std::string cmd("dot -Tpng");
cmd += " "+filename+" -o"+filename+".png";
std::system(cmd.c_str());
std::cout << "tree saved in file: " << filename<< std::endl;
}
};
std::string BTreePrinter::fontColor="black",BTreePrinter::fillColor="#FFFFFF",BTreePrinter::width="0.5",
BTreePrinter::height="0.5",BTreePrinter::fontsize="16",
BTreePrinter::edgeColor="blue",BTreePrinter::arrowheadType="normal";
long BTreePrinter::fileCounter = 1;
std::string BTreePrinter::prefix="B-Tree";
#endif /* BTREEPRINTER_H_ */
转换成的dot文件如下所示:
/************************************************ Auto generated by my program which transfer B-Tree to dot file. Author: wangdq Time: 2015-06-08 CSDN: http://blog.csdn.net/wangdingqiaoit ************************************************/
digraph BTree {
label="(1) initial B-Tree";labelloc=b;labeljust=center;
ordering=out
node[shape=record,width=0.5,height=0.5,fontsize=16,style="filled", fillcolor="#FFFFFF",fontcolor="black"];
edge[color="blue", arrowhead="normal"];
16[label="<f0> 16"];
3[label="<f0> 3|<f1> 8"];
22[label="<f0> 22|<f1> 25"];
1[label="<f0> 1|<f1> 2"];
5[label="<f0> 5|<f1> 6|<f2> 7"];
13[label="<f0> 13|<f1> 14|<f2> 15"];
18[label="<f0> 18|<f1> 20"];
23[label="<f0> 23|<f1> 24"];
27[label="<f0> 27|<f1> 37"];
/* edges*/
"16":f0:sw->"3":f1
"16":f0:se->"22":f1
"3":f0:sw->"1":f1
"3":f1:sw->"5":f2
"3":f1:se->"13":f1
"22":f0:sw->"18":f1
"22":f1:sw->"23":f1
"22":f1:se->"27":f1
}