首页 > 其他分享 >带权树的过滤

带权树的过滤

时间:2022-12-19 12:13:38浏览次数:33  
标签:Node 带权 id add 过滤 np new 节点

带权树形结构的过滤

1. 带权树

1.1. 树的概念

树是一种数据结构,它是由n(n≥0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  • 每个节点有零个或多个子节点。
  • 没有父节点的节点称为根节点。
  • 每一个非根节点有且只有一个父节点。

1.2 带权树的概念

在数的基础上,每个节点都都带有权重,也就是数值。

2. 树的生成(将集合转化为树)

2.1 数据库对象

用来接收数据库中的数据,一般称为DO对象。

public class Node {
    private Integer id;
    private Integer weight; // 权重
    private Integer pid; //父级id

    public Node(Integer id, Integer weight, Integer pid) {
        this.id = id;
        this.weight = weight;
        this.pid = pid;
    }

    public static Node createRootNode(){
        return new Node(0,0,null);
    }
}

2.2 树形节点结构

表示树中的每一个节点,用于将DO对象转化为节点。

public class NodeTree {
    private Integer id;
    private Integer weight;
    private Integer pid;
    private List<NodeTree> childrens;

    public NodeTree(Node node) {
        this.id = node.getId();
        this.weight = node.getWeight();
        this.pid = node.getPid();
    }
}

2.3 生成树

因为我们一般的数据都保存在数据库库中,查询出来的数据是按行分布的,通常都使用 List 集合来接收,也就是说我们需要将在代码中将其转化为树形结构。一共有如下几步:

  1. 将 List 集合 转化为 Map 集合,方便我们随机读取,同时每行数据转化为树形节点。
  2. 通过 Map 集合生成树。
  3. 通过 DFS 遍历树添加权重。

代码如下:

public static void main(String[] args) {
    //初始数据
    ArrayList<Node> nodeList = new ArrayList<Node>(){{
        add(new Node(1,1,0));
        add(new Node(10,10,0));
        add(new Node(2,2,1));
        add(new Node(3,3,2));
        add(new Node(4,4,10));
        add(Node.createRootNode()); //添加根节点
    }};

    //转化为map方便随机读取
    Map<Integer,NodeTree> nodeMap = new HashMap<>();
    for(Node n : nodeList){
        nodeMap.put(n.getId(),new NodeTree(n));
    }

    //转化链式结构为树形结构
    for (Node n : nodeList) {
        Integer pid = n.getPid();
        if (Objects.nonNull(pid)) { //pid为null为根节点
            NodeTree pTree = nodeMap.get(pid);
            if (pTree.getChildrens() == null) { //childrens为null表示没有子节点
                pTree.setChildrens(new ArrayList<>());
            }
            NodeTree currentNodeTree = nodeMap.get(n.getId());
            pTree.getChildrens().add(currentNodeTree); //添加子节点到父节点中
        }
    }

    //id为0的是根节点
    //添加权重
    dfsTree(nodeMap.get(0));

    String jsonStr = JSONUtil.toJsonStr(nodeMap.get(0));
    JSONObject jsonObject = new JSONObject(jsonStr);
    System.out.println(JSONUtil.toJsonPrettyStr(jsonObject.toString()));
}

public static Integer dfsTree(NodeTree root){
    if(root == null){
        return 0;
    }
    if(root.getChildrens() != null){
        for(NodeTree childe : root.getChildrens()){
            root.setWeight(root.getWeight() + dfsTree(childe));
        }
    }
    return root.getWeight();
}

输出如下:

{
    "weight": 20,
    "childrens": [
        {
            "weight": 6,
            "pid": 0,
            "childrens": [
                {
                    "weight": 5,
                    "pid": 1,
                    "childrens": [
                        {
                            "weight": 3,
                            "pid": 2,
                            "id": 3
                        }
                    ],
                    "id": 2
                }
            ],
            "id": 1
        },
        {
            "weight": 14,
            "pid": 0,
            "childrens": [
                {
                    "weight": 4,
                    "pid": 10,
                    "id": 4
                }
            ],
            "id": 10
        }
    ],
    "id": 0
}

3. 树的过滤

现在只想要目标集合的节点路径,不在目标集合中路径就不显示。之前我们是通过循环生成树,现在我们需要换一种思路,通过目标集合中的节点向上遍历,直到遇到根节点,来生成树。需要注意的时,当树的层数达到三层时,可能会重复添加子节点,所以子集合需要去重。

public static void main(String[] args) {
    //初始数据
    ArrayList<Node> nodeList = new ArrayList<Node>(){{
        add(new Node(1,1,0));
        add(new Node(10,10,0));
        add(new Node(2,2,1));
        add(new Node(3,3,2));
        add(new Node(4,4,10));
        add(Node.createRootNode()); //添加根节点
    }};

    //过滤数据
    ArrayList<Node> nodeListFilter = new ArrayList<Node>(){{
        add(new Node(3,3,2));
    }};


    //转化为map方便随机读取
    Map<Integer,NodeTree> nodeMap = new HashMap<>();
    for(Node n : nodeList){
        nodeMap.put(n.getId(),new NodeTree(n));
    }

    //转化链式结构为树形结构
    for(Node n : nodeListFilter){
        dfsTreeFilterForm(nodeMap.get(n.getId()),nodeMap);
    }

    //id为0的是根节点
    //添加权重
    dfsTree(nodeMap.get(0));

    String jsonStr = JSONUtil.toJsonStr(nodeMap.get(0));
    JSONObject jsonObject = new JSONObject(jsonStr);
    System.out.println(JSONUtil.toJsonPrettyStr(jsonObject.toString()));

}

public static Integer dfsTree(NodeTree root){
    if(root == null){
        return 0;
    }
    if(root.getChildrens() != null){
        for(NodeTree childe : root.getChildrens()){
            root.setWeight(root.getWeight() + dfsTree(childe));
        }
    }
    return root.getWeight();
}

//向上遍历
private static void dfsTreeFilterForm(NodeTree n, Map<Integer, NodeTree> nodesMap){
    NodeTree np = nodesMap.get(n.getPid());
    if(np.getChildrens() == null){
        np.setChildrens(new ArrayList<>());
    }
    List<NodeTree> children = np.getChildrens();
    if (!children.contains(n)){ //避免重复添加子节点,因为在向上遍历时,其同级节点已经向上遍历过了,如果不做判断,其父级的往上的都会重复添加父级节点。
        children.add(n);
    }
    if(np.getPid() != null){
        dfsTreeFilterForm(np,nodesMap);//向上传递
    }
}

输出如下

{
    "weight": 6,
    "childrens": [
        {
            "weight": 6,
            "pid": 0,
            "childrens": [
                {
                    "weight": 5,
                    "pid": 1,
                    "childrens": [
                        {
                            "weight": 3,
                            "pid": 2,
                            "id": 3
                        }
                    ],
                    "id": 2
                }
            ],
            "id": 1
        }
    ],
    "id": 0
}

4. 树过滤的优化

4.1 优化一:合并步骤2和步骤3

进行过滤的时候,因为是向上遍历的,完全可以将步骤2和步骤3合并起来,将构建树的过程中同时加上权重。修改上面方法,添加np.setWeight(np.getWeight()+n.getWeight());到 dfsTreeFilterForm() 中,不需要调用 dfsTree() 了;

public static void main(String[] args) {
    //初始数据
    ArrayList<Node> nodeList = new ArrayList<Node>(){{
        add(new Node(1,1,0));
        add(new Node(10,10,0));
        add(new Node(2,2,1));
        add(new Node(3,3,2));
        add(new Node(4,4,10));
        add(Node.createRootNode()); //添加根节点
    }};

    //过滤数据
    ArrayList<Node> nodeListFilter = new ArrayList<Node>(){{
        add(new Node(3,3,2));
    }};


    //转化为map方便随机读取
    Map<Integer,NodeTree> nodeMap = new HashMap<>();
    for(Node n : nodeList){
        nodeMap.put(n.getId(),new NodeTree(n));
    }

    //转化链式结构为树形结构
    for(Node n : nodeListFilter){
        dfsTreeFilterForm(nodeMap.get(n.getId()),nodeMap);
    }

    String jsonStr = JSONUtil.toJsonStr(nodeMap.get(0));
    JSONObject jsonObject = new JSONObject(jsonStr);
    System.out.println(JSONUtil.toJsonPrettyStr(jsonObject.toString()));

}

//向上遍历
private static void dfsTreeFilterForm(NodeTree n, Map<Integer, NodeTree> nodesMap){
    NodeTree np = nodesMap.get(n.getPid());
    if(np.getChildrens() == null){
        np.setChildrens(new ArrayList<>());
    }
    List<NodeTree> children = np.getChildrens();
    if (!children.contains(n)){ //避免重复添加子节点,因为在向上遍历时,其同级节点已经向上遍历过了,如果不做判断,其父级的往上的都会重复添加父级节点。
        children.add(n);
    }
    if(np.getPid() != null){
        dfsTreeFilterForm(np,nodesMap);//向上传递
    }
}

4.2 优化二:取消 contains() 的遍历,修改 list 为 set。

再重复添加子节点的判断这里之前使用的 contains(),每次都要遍历 list。现在改为 set 集合,每次 add() 时,如果返回 false 就表明之前已经添加过了,我们就不在添加权重,同时也避免了循环。

private static void dfsTreeFilterForm(NodeTree n, Map<Integer, NodeTree> nodesMap){
    NodeTree np = nodesMap.get(n.getPid());
    if(np.getChildrens() == null){
        np.setChildrens(new HashSet<>());
    }
    Set<NodeTree> children = np.getChildrens();
    if(children.add(n)){ //避免重复添加子节点,因为在向上遍历时,其同级节点已经向上遍历过了,如果不做判断,其父级的往上的都会重复添加父级节点。
        np.setWeight(np.getWeight()+n.getWeight());
    }
    if(np.getPid() != null){
        dfsTreeFilterForm(np,nodesMap);//向上传递
    }
}

标签:Node,带权,id,add,过滤,np,new,节点
From: https://www.cnblogs.com/theheboy/p/16991837.html

相关文章