数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )

可视化树的算法有很多,这里我们借助Graphviz实现二叉搜索树BST(二叉树)和B-Tree(多叉树)的快速可视化。关于一般的算法,可以到知网或者万方数据中去查阅,不在此处详谈。

基本的思想就是利用Gphviz可视化树的基本思想,将树转换为dot文件,然后利用Graphviz将其转换为图片。这种方法的特点就是,简单,快速,高效。这种方法可以用于辅助理解程序运行过程、调试程序。
请在使用前确认,你的系统已经正确安装了Graphviz,并配置了dot命令。
下面的程序依赖于命令:

dot example.dot -Tpng -o 1.png

完整的代码地址,可以在github BST和github BTree得到。
下面给出一些例子,文章末尾也摘取了c++实现的自动转换代码。

1. 例子

遍历可视化

将转换的图片,链接成gif图片,即可得到运行时动态效果:

先序遍历:

广度优先遍历:

AVL可视化

这里写图片描述
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第1张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第2张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第3张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第4张图片

DSW算法可视化

数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第5张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第6张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第7张图片
省略中间过程,创建主链为:

中间过程省略,则最终生成的平衡BST为:
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第8张图片

Splay Tree例子此处省略。

B-Tree可视化

数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第9张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第10张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第11张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第12张图片
数据结构与算法14: 利用Graphviz快速可视化树(visualizing Trees with Graphviz easily )_第13张图片

注意下面的代码,给出了转换部分的实现;转换部分依赖于你的节点的定义,你可以根据需要调整。在你程序中合适地方插入toPng函数来生成图片。

2. 二叉树可视化实现

定义文件:

/** * 二叉树搜索树结点类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
}

3. B-Tree转换实现

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
}

你可能感兴趣的:(数据结构)