问题
用java实现文本形式的树状结构图(如图所示要求用递归)
方法1
代码
public class Test {
public static void main(String[] args) {
// 分类列表
List<Category> categoryList = new ArrayList<>();
// 一级分类
Category oneCategory1 = new Category(1, 0, "Java工程师招聘试题", 1);
// 二级分类
Category twoCategory1 = new Category(2, 1, "Java面试题", 2);
Category twoCategory2 = new Category(3, 1, "简答题", 2);
Category twoCategory3 = new Category(4, 1, "Java面试题", 2);
// 三级分类(属于“Java面试题”)
Category threeCategory1 = new Category(5, 2, "选择题", 3);
Category threeCategory2 = new Category(6, 2, "程序分析题", 3);
// 四级分类(属于“选择题”)
Category fourCategory1 = new Category(7, 5, "JS选择题", 4);
// 五级分类(属于“JS选择题”)
Category fiveCategory1 = new Category(8, 7, "HTML选择题", 5);
Category fiveCategory2 = new Category(9, 7, "WebService选择题", 5);
// 填充数据,最终实现categoryList就像是从数据库中查询出的集合这种效果
categoryList.add(oneCategory1);
categoryList.add(twoCategory1);
categoryList.add(twoCategory2);
categoryList.add(twoCategory3);
categoryList.add(threeCategory1);
categoryList.add(threeCategory2);
categoryList.add(fourCategory1);
categoryList.add(fiveCategory1);
categoryList.add(fiveCategory2);
// 往children中填充数据
List<Category> list = build(categoryList);
// 存储数据
StringBuilder sb = new StringBuilder();
// 将数据添加到可变字符串中
add(sb, list, "");
// 展示数据
System.out.println(sb.toString());
}
/**
* 将数据添加到可变字符串中
*
* @param sb 存储输出信息的可变字符串
* @param list 分类列表
* @param prefix 当前分类前面的修饰图形
*/
private static void add(StringBuilder sb, List<Category> list, String prefix) {
for (int i = 0; i < list.size(); i++) {
// 对一级分类需要特殊处理
if (list.get(i).getPid() == 0) {
// 根据一级分类位置判断是否需要换行
sb.append(i == 0 ? "" : "\n");
// 对一级分类特殊处理
sb.append(list.get(i).getName());
// 添加子分类列表信息到可变字符串中
add(sb, list.get(i).getChildren(), "\n ");
} else {
// 添加当前分类前面的修饰图形(不包括和当前分类最近的那个图形)
sb.append(prefix);
// 添加距离当前分类最近的那个图形
sb.append(i < list.size() - 1 ? "├" : "└");
// 添加当前分类名称
sb.append(list.get(i).getName());
// 子分类前面的修饰图形(不包括和子分类最近的那个图形)
String sonPrefix = prefix + ((i == list.size() - 1) ? " " : "│");
// 添加子分类列表信息到可变字符串中
add(sb, list.get(i).getChildren(), sonPrefix);
}
}
}
/**
* 构建多级分类菜单
*
* @param categoryList 所有的分类列表
* @return
*/
private static List<Category> build(List<Category> categoryList) {
List<Category> collect = categoryList.stream()
// 找出所有一级分类
.filter(category -> category.getPid() == 0)
// 向一级分类中填充孙子分类列表
.map(category -> {
category.setChildren(getChildren(category, categoryList));
return category;
})
// 排序保证同级分类顺序不会改变
.sorted((c1, c2) -> c1.getId().compareTo(c2.getId())).collect(Collectors.toList());
return collect;
}
/**
* 获取子分类列表
*
* @param category 当前分类
* @param categoryList 所有的分类列表
* @return
*/
private static List<Category> getChildren(Category category, List<Category> categoryList) {
List<Category> collect = categoryList.stream()
// 通过过滤得到当前分类的所有子分类
.filter(item -> item.getPid().equals(category.getId()))
// 向当前子分类中填充孙子分类列表
.map(item -> {
item.setChildren(getChildren(item, categoryList));
return item;
})
// 排序保证同级分类顺序不会改变
.sorted((c1, c2) -> c1.getId().compareTo(c2.getId())).collect(Collectors.toList());
return collect;
}
}
class Category {
/**
* 分类id
*/
private Integer id;
/**
* 父分类id
*/
private Integer pid;
/**
* 分类名称
*/
private String name;
/**
* 分类级别
*/
private Integer level;
/**
* 当前分类的子分类
*/
private List<Category> children;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
public List<Category> getChildren() {
return children;
}
public void setChildren(List<Category> children) {
this.children = children;
}
public Category(Integer id, Integer pid, String name, Integer level) {
this.id = id;
this.pid = pid;
this.name = name;
this.level = level;
}
}
结果
方法2
代码
public class Father {
private String title;
private List<Father> children = new ArrayList<>();
private Father(String title) {
this.title = title;
}
private void addChild(Father f) {
children.add(f);
}
private String toString(String self, String son) {
StringBuilder b = new StringBuilder();
// 为自己铺剩余前路
b.append(self);
// 为自己铺中间的路
b.append(title);
// 为自己铺后路
b.append("\n");
// 如果父亲(当前this)有儿子
if (children.size() > 0) {
// 如果只有一个儿子,不会执行for循环,或者为前n-1个儿子执行for循环
for (int i = 0; i < children.size() - 1; i++) {
// 为儿子铺部分前路
b.append(son);
// 前n-1个儿子将成为父亲,按照自己的意愿去为自己和儿子铺路
b.append(children.get(i).toString("├", son + "│"));
}
// 如果只有一个儿子,直接执行下面的代码,或者为第n个儿子执行以下代码
// 为儿子铺部分前路
b.append(son);
// 第n个儿子将成为父亲,按照自己的意愿去为自己和儿子铺路
b.append(children.get(children.size() - 1).toString("└", son + " "));
}
return b.toString();
}
public static void main(String[] args) {
// 0级菜单
Father root = new Father("JAVA工程师招聘试题");
// 1级菜单
Father f1 = new Father("Java面试题");
root.addChild(f1);
// 2级菜单
Father f1_1 = new Father("选择题");
f1.addChild(f1_1);
// 3级菜单
Father f1_1_1 = new Father("JS选择题");
f1_1.addChild(f1_1_1);
// 4级菜单
Father f1_1_1_1 = new Father("HTML选择题");
f1_1_1.addChild(f1_1_1_1);
// 4级菜单
Father f1_1_1_2 = new Father("WebService选择题");
f1_1_1.addChild(f1_1_1_2);
// 2级菜单
Father f1_2 = new Father("程序分析题");
f1.addChild(f1_2);
// 1级菜单
Father f2 = new Father("简答题");
root.addChild(f2);
// 1级菜单
Father f3 = new Father("编程题");
root.addChild(f3);
System.out.println(root.toString("", " "));
}
}
结果
解释
提醒:下面的解释主要针对方法2,但是方法1的思想也是取自这种多级菜单思路
Father类
中有children属性
,这个属性是List<Father>类型
的,然后children
属性中的Father对象
中又有children属性
,所以和平时网站中的导航栏中菜单是类似的,因此我使用上面的图来表示,我们在main()方法中执行代码的最终结果就是上图这样子,所以困难并不在main()方法中,而在toString()中,在里面我把上级菜单比喻成父亲,而把子级菜单比喻成儿子,然后用现实生活中的"铺路"
来给大家解释,我通过写注释的方式来给大家串一下代码,如下图:
其实这种问题我们平时编代码的时候也会遇到,不过不会遇见这种变态的,毕竟里面还有特殊符号,还有处理的方式也不一样,实在变态啊,我们拿爱奇艺举例:
如果你还看不出来,来看图: