树练习

背景:工作中要用到树结构自己却不熟悉,学习网上代码后进行练习。

技术栈:SpringBoot搭架子,基础Java+JavaScript+HTML代码。

后端:

Model模型+业务逻辑层:

1.树节点定义:TreeNode.java:

public class TreeNode {
    private int id;
    private String name;
    private TreeNode parent;
    private ArrayList<TreeNode> children;

    public TreeNode(int id, String name) {
        this.id = id;
        this.name = name;
        this.children = new ArrayList<>();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public TreeNode getParent() {
        return parent;
    }

    public void setParent(TreeNode parent) {
        this.parent = parent;
    }

    public ArrayList<TreeNode> getChildren() {
        return children;
    }

    public void setChildren(ArrayList<TreeNode> children) {
        this.children = children;
    }

    @Override
    public String toString() {
        return "TreeNode{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    /**
     * 添加一个子节点
     * @param treeNode
     */
    public void addOneChildNode(TreeNode treeNode) {
        this.children.add(treeNode);
        treeNode.setParent(this);
    }

    /**
     * 删除一个子节点
     * @param treeNode
     */
    public void deleteOneChildNode(TreeNode treeNode) {
        this.children.remove(treeNode);
        treeNode.setParent(null);
    }
}

2.树定义:Tree.java:

public class Tree {
    private TreeNode root;
    /**
     * 节点id
     */
    static private int nodeIdCounter = 1;

    public Tree() {
        this.root = null;
    }

    public TreeNode getRoot() {
        return root;
    }

    public void setRoot(TreeNode root) {
        this.root = root;
    }

    /**
     * 遍历全树
     * @return
     */
    public List<String> traverseWholeTree() {
        List<String> nodeDescList = new ArrayList<>();
        this.traverseWholeTreeRecursivelyHelper(this.root, nodeDescList);
        return nodeDescList;
    }

    /**
     * 遍历树具体方法:递归实现
     *
     * 深度优先遍历 中的 前序遍历。
     * 深度优先遍历:尽可能深地访问图的分支,直到到达末端节点(叶子节点),再回溯并继续遍历其他分支。
     * 在树结构中,深度优先遍历通常通过递归来实现。
     * 前序遍历、中序遍历、后序遍历,都是深度优先遍历的具体实现方式,区别在于访问根节点的顺序。
     * 前序遍历中,先访问根节点,再递归地访问左子树和右子树。
     *
     * @param treeNode
     * @param nodeDescList
     */
    public void traverseWholeTreeRecursivelyHelper(TreeNode treeNode, List<String> nodeDescList) {
        if (treeNode != null) {
            nodeDescList.add(treeNode.toString());
            ArrayList<TreeNode> childrenList = treeNode.getChildren();
            // 这里练习下防御性编程,尽管TreeNode构造方法里会创建空列表作为children,也就是说.getChildren肯定不会返回null
            if (childrenList != null) {
                for (TreeNode child : childrenList) {
                    traverseWholeTreeRecursivelyHelper(child, nodeDescList);
                }
            }
        }
    }

    /**
     * 根据id获取树节点
     * @param idToBeSearch 要查找的节点id
     * @return
     */
    public TreeNode getTreeNodeById(int idToBeSearch) {
        if (this.root == null) {
            return null;
        }
        return getTreeNodeByIdRecursivelyHelper(this.root, idToBeSearch);
    }

    /**
     * 递归查找,根据id获取树节点
     * @param treeNode
     * @param idToBeSearch
     * @return
     */
    public TreeNode getTreeNodeByIdRecursivelyHelper(TreeNode treeNode, int idToBeSearch) {
        if (treeNode.getId() == idToBeSearch) {
            return treeNode;
        }
        ArrayList<TreeNode> children = treeNode.getChildren();
        for (TreeNode child : children) {
            TreeNode treeNodeFindResult = getTreeNodeByIdRecursivelyHelper(child, idToBeSearch);
            if (treeNodeFindResult != null) {
                return treeNodeFindResult;
            }
        }
        return null;
    }

    /**
     * 添加根节点
     * @param name 根节点名称
     * @return
     */
    public List<String> addRoot(String name) {
        TreeNode root = new TreeNode(nodeIdCounter++, name);
        this.setRoot(root);
        return this.traverseWholeTree();
    }

    /**
     * 添加节点
     * @param parentNodeId 父节点id
     * @param name 节点名称
     * @return
     */
    public List<String> addNode(int parentNodeId, String name) {
        List<String> treeDescList = new ArrayList<>();
        if (this.getRoot() == null) {
            treeDescList.add("root is null, please add root first");
            return treeDescList;
        }
        TreeNode parentTreeNode = this.getTreeNodeById(parentNodeId);
        if (parentTreeNode == null) {
            treeDescList.add("parent node id not found!");
            return treeDescList;
        }
        TreeNode treeNodeNew = new TreeNode(nodeIdCounter++, name);
        // 添加子节点
        parentTreeNode.addOneChildNode(treeNodeNew);
        return this.traverseWholeTree();
    }

    /**
     * 删除节点
     * @param idToBeDelete 要删除的节点id
     * @return
     */
    public List<String> deleteNodeById(int idToBeDelete) {
        // 删除描述
        List<String> treeDescList = new ArrayList<>();
        if (this.getRoot().getId() == idToBeDelete) {
            this.setRoot(null);
            return this.traverseWholeTree();
        }
        TreeNode nodeToBeDelete = this.getTreeNodeById(idToBeDelete);
        if (nodeToBeDelete == null) {
            treeDescList.add("node id not find!");
            return treeDescList;
        }
        TreeNode parentNode = nodeToBeDelete.getParent();
        // 删除子节点
        parentNode.deleteOneChildNode(nodeToBeDelete);
        return this.traverseWholeTree();
    }
}

Control控制层:

3.树控制器:TreeController.java:

@RestController
@RequestMapping("/tree")
public class TreeController {
    private Tree tree = new Tree();

    /**
     * 遍历树
     * @return
     */
    @GetMapping("traverseWholeTree")
    public List<String> traverseWholeTree() {
        return tree.traverseWholeTree();
    }

    /**
     * 添加根节点
     * @param treeNodeRequest 根节点DTO
     * @return
     */
    @PostMapping("addRoot")
    public List<String> addRoot(@RequestBody TreeNodeRequest treeNodeRequest) {
        return tree.addRoot(treeNodeRequest.getName());
    }

    /**
     * 添加节点
     * @param treeNodeRequest 节点DTO
     * @return
     */
    @PostMapping("addNode")
    public List<String> addNode(@RequestBody TreeNodeRequest treeNodeRequest) {
        return tree.addNode(treeNodeRequest.getParentNodeId(), treeNodeRequest.getName());
    }

    /**
     * 删除节点
     * @param idToBeDelete 要删除的节点id
     * @return
     */
    @DeleteMapping("deleteNodeById/{idToBeDelete}")
    public List<String> deleteNodeById(@PathVariable int idToBeDelete) {
        return tree.deleteNodeById(idToBeDelete);
    }
}

View视图层:

4.树数据传输DTO:TreeNodeRequest.java:

public class TreeNodeRequest {
    // 父节点id
    private int parentNodeId;
    // 节点名
    private String name;

    public int getParentNodeId() {
        return parentNodeId;
    }

    public void setParentNodeId(int parentNodeId) {
        this.parentNodeId = parentNodeId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

前端:

5.页面:index.html:

<html>
<body>
<h1>Tree Practice</h1>
<button onclick="addRoot()">add root</button>
<button onclick="addNode()">add node</button>
<button onclick="deleteNodeById()">delete node</button>
<button onclick="traverseWholeTree()">traverse whole tree</button>
</body>
<script>
    async function traverseWholeTree() {
        // const response = await fetch('http://localhost:8080/tree/traverseWholeTree/');
        const response = await fetch(`/tree/traverseWholeTree/`);
        const nodeDescArray = await response.json();
        // alert('nodeDescArray is array:' + Array.isArray(nodeDescArray));
        // alert('nodeDescArray is array:' + (nodeDescArray instanceof Array));
        const nodeDescStr = nodeDescArray.length > 0 ? nodeDescArray.join('\n') : 'tree is empty';
        alert(nodeDescStr);
    }

    async function addRoot() {
        const nodeName = prompt("please input root node name:");
        // todo 校验
        const response = await fetch(`/tree/addRoot`, {
            method: 'POST',
            headers: {
                'Content-type': 'application/json'
            },
            body: JSON.stringify({
                name: nodeName
            })
        });
        const result = await response.json();
        const resultStr = await result.join('/n');
        alert(resultStr);
    }

    async function addNode() {
        const parentNodeId = prompt("please input parentNodeId:");
        const nodeName = prompt("please input node name:");
        // todo 校验
        const response = await fetch(`/tree/addNode`, {
            method: 'POST',
            headers: {
                'Content-type': 'application/json'
            },
            body: JSON.stringify({
                parentNodeId: parentNodeId,
                name: nodeName
            })
        });
        const result = await response.json();
        const resultStr = result.join('\n');
        alert(resultStr);
    }

    async function deleteNodeById() {
        const parentNodeId = prompt("please input toBeDeleteNodeId:");
        // todo 校验
        const response = await fetch(`/tree/deleteNodeById/${parentNodeId}`, {
            method: 'DELETE'
        });
        const result = await response.json();
        const resultStr = result.join('\n');
        alert(resultStr);
    }
</script>
</html>

6.页面效果:

7.测试用例:

 

posted on 2025-04-03 21:13  平凡力量  阅读(24)  评论(0)    收藏  举报