树结构的子树合并(不考虑子树顺序)

来源:互联网 发布:外教口语网络平台 编辑:程序博客网 时间:2024/05/03 13:24


问题提出

        最近工作上遇到个问题,分析数据链路的服务调用情况,并对相同的服务路径进行合并,具体的一次服务调用的部分数据如下(省略部分字段),一条数据链路通常会有成千上万条类似的记录,表示一个前台请求被处理所调用的服务流程,一个记录代表依次服务调用。rpc_id是一个关键的字段,代表了服务调用层次,比如9.4.1是被9.4调用,而9.4又被9调用,9就是发起服务调用的根节点,这是一个典型的树结构。展示成千上万个节点的树结构没太大意义,因为有很多节点可以合并,并且子树也可以合并,合并之后的服务调用树并不大。关于合并规则,节点是否等价要求满足:①、有相同的父节点,②、server字段相同,③、method字段相同;等价的子树可以合并,子树等价条件是要求从子树根节点到叶节点的路径上对应子节点等价,不考虑子节点之间的顺序。

rpc_idservermethod90983589d-94f7-4bff-addc-1981014bd075MQR***********************5B439.17204504b-ea03-4469-91ea-08306480e9c3co****************************~S9.27278962404b-ea03-4469-91ea-08385719c3TD8&*********************uth9.47204504b-ea03-4469-91ea-08306497180e9c3co**************************~S9.4.183589d-94f7-4bff-addc-1981014bdTD**************uth

 表一 部分生产数据

         直接判断两个节点是否等价不难,在同一个节点下的子节点肯定满足条件①,有相同的父节点,接着判断对应的server字段和method字段是否相等即可。难的是判断以此节点为根节点的子树是否等价,这不是二叉树,亦不考虑子节点之间的顺序,均增加了问题的难度。

问题分析

        根据问题描述,抽象出一个具体的实例,进行具体分析,寻找解决思路。构造一棵符合问题描述的树如下图所示:
tree_demo
图一  问题模拟树
        图中节点的文本模拟了表一中的rpc_id,rpc_id最大的意义在于定位节点在树中的层次。实际的生产数据是一行一行的记录,并不是一个自然地树结构,我便是依靠rpc_id构建起来的整颗树,然后对其进行合并操作。图一作为对实际问题的抽象,颜色相同的节点表示子树节点值相同(即问题描述中节点等价的2、3条件,但是不一定能够合并,因为不一定满足1、有相同的父节点)。比如对图一中的1.1.1.2节点和1.1.2.1节点,值相同,但拥有不同的父节点,不等价,无法合并。
        根据合并规则,图一中有两个等价的子树:1.1.1和1.1.2等价,1.1和1.2等价,最后的合并结果是把以1.1为根节点的子树删除,保留根节点1的1.2和1.3子树(结果图画起来太麻烦,我偷个懒,不再画了)。在问题中rpc_id的值的意义仅仅是表现树的层次结果,最终的结果也要求整个树的层次结构,对于在合并过程中保留哪棵子树(也就是使用哪个rpc_id)并没有要求。
        树结构问题的解决方式很自然的想到使用递归的方法,其实这也是一个递归的问题。判断子树1.1和子树1.2是否等价,除了判断节点1.1和节点1.2是否等价外,还要判断他们是否有完全对应的子节点。即在1.2的子树中是否有与1.1的子树1.1.1、子树1.1.2和子树1.1.3等价的树,如果有完全对应等价的子树(子树1.1的每一棵子树唯一地与1.2中的某一棵子树等价,子树1.2中的每一棵子树唯一地与1.1中的某一棵子树等价)。直接看子树1.1和子树1.2并不对应,因为子树数量不同。但实际上子树1.1的两课子树1.1.1和1.1.2等价,是必须合并的。应该对合并子树的过程进行后序遍历。比如要合并根节点1的子树,要保证先完成对其子节点1.1、1.2和1.3的子树合并,在1.1、1.2和1.3的子树中不存在等价的子树。
        合并的过程需要递归遍历子树,判断子树等价的过程需要递归遍历子树。判断子树是否等价是合并子树的前提。不同于子树合并,判断子树是否等价需要使用树的先序遍历,比如判断子树1.1.1和子树1.1.3是否等价时,先判断节点1.1.1和节点1.1.3是否等价,因为两个节点值不同(不同颜色),便不需要再继续遍历判断这两个节点的子树是否等价了。这就是树的遍历过程中的剪枝。依赖于合并子树的后序遍历操作,当判断子树1.1.1和子树1.1.3是否等价时,已经完成了对节点1.1.1和节点1.1.3的子树合并操作。下一节将给出具体的实现代码,结合图一,很容易理解整个合并过程。

代码实现

        下面给出Java实现代码,使用具体的语言描述问题可以展现更多细节,故不再使用伪代码。

         
import java.util.LinkedList;import java.util.List;public class TreeNode {    private String key;         //存储rpc_id,标识树节点的结构层次    private String value;       //存储树节点的值,简化的数据模型    private List<TreeNode> children;  //存储子节点        public TreeNode(String strKey,String strValue) {        children = new LinkedList<TreeNode>();        key = strKey;        value = strValue;    }        public int getChildrenCount() {        return this.children.size();    }        public String getValue() {        return this.value;    }        //合并子树    public void MergeChildren() {        //对树进行后序遍历,先合并子树        int cCount = this.children.size();        for(int i=0;i<cCount;i++) {            TreeNode node = children.get(i);            node.MergeChildren();        }                //保证子树节点中没有等价的子树,即完成        //子树节点的子树合并,再来进行本节点的子树合并        //合并子树中所有可以合并的子树        for(int i=0;i<children.size()-1;i++) {            TreeNode child1 = children.get(i);            for(int j=i+1;j<children.size();j++) {                TreeNode child2 = children.get(j);                if(child1.IsTheSameSubTree(child2)) {                    children.remove(j);                    j--;                }            }        }    }        //判断子树是是否等价    private boolean IsTheSameSubTree(TreeNode another) {        //一个为null,一个不为null,不等价        if(another==null) return false;                //子节点数量不同,不等价        if(this.getChildrenCount() != another.getChildrenCount())            return false;                //子节点的值不相等,不等价        if(!IsNodeSame(another)) return false;                //完成树节点的先序遍历,再就行子节点的遍历        List<TreeNode> anotherChildren = another.getChildren();        for(TreeNode myChild : children) {            //对本节点的每一个子树,在对比节点中寻找等价的子树,            //如果找不到,则本节点与对比节点不等价            boolean bFind = false;            for(TreeNode anotherChild:anotherChildren) {                if(myChild.IsTheSameTrace(anotherChild)) {                    bFind = true;                    break;                }            }            if(bFind==false) return false;        }        return true;    }        private boolean IsNodeSame(TreeNode another) {        return value.equals(another.getValue());    }}

        读者可以执行TreeNode(1.1.1).IsTheSameSubTree(TreeNode(1.1.2)),节点不多,很容易得出是否等价的结果,可知1.1.1和1.1.2子树等价直接将子树1.1.2删除,即合并1.1.1和1.1.2;再执行TreeNode(1.1.1).IsTheSameSubTree(TreeNode(1.1.3)),子树不等价;回溯至根节点,判断TreeNode(1.1).IsTheSameSubTree(TreeNode(1.2));可知1.1与1.2亦等价,删除子树1.2。再合并1.2和1.1时要保证完成了对1.2和1.1的子节点的合并操作,否则无法判断1.2和1.1节点是否等价,合并使用树的后序遍历。

1 0
原创粉丝点击