树练习
背景:工作中要用到树结构自己却不熟悉,学习网上代码后进行练习。
技术栈: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.测试用例:

浙公网安备 33010602011771号