1. 程式人生 > 其它 >java 遞迴實現樹形結構的兩種實現方式

java 遞迴實現樹形結構的兩種實現方式

1.情景展示

樹形結構,和我們平常所觸及到的無限級選單,是同一個道理。

所謂樹形結構,我們可以將其理解為:樹根或者樹冠,都可以無限分叉下去。

現有一張表,需要對錶中資料進行分級查詢(按照上下級關係進行排列),我們常用的資料庫有:oracle和mysql;

如果使用oracle的話,使用connect by,很容易就能做到;

但是,mysql沒有現成的遞迴函式,需要我們自己封裝,而且,就算封裝好了遞迴函式,mysql在執行的時候,查詢速度會很慢。

如何解決這個問題呢?

既然資料庫不給力,我們只能交由程式來處理了,以減輕mysql資料庫的壓力。

2.模擬資料

java實體類

/**
 * 選單類
 * @description:
 * @author: Marydon
 * @date: 2022-05-17 15:47
 * @version: 1.0
 * @email: [email protected]
 */
@Getter// 生成成員變數的get方法
@Setter// 生成成員變數的set方法
@NoArgsConstructor// 生成類的無參構造方法
public class Menu {
    // id
    public Integer id;
    // 名稱
    public String name;
    // ⽗id ,根節點為0
    public Integer parentId;
    // ⼦節點資訊
    public List<Menu> childList;
    // 當實體類宣告有參構造方法時,無參構造方法會自動消失,如果用得到,需要重新生成
    public Menu(Integer id, String name, Integer parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }
}    

模擬已從資料庫查詢到資料

public static void main(String[] args) {
    //模擬從資料庫查詢出來
    List<Menu> menus = Arrays.asList(
            new Menu(1, "根節點", 0),
            new Menu(2, "一級節點1", 1),
            new Menu(3, "二級節點1.1", 2),
            new Menu(4, "二級節點1.2", 2),
            new Menu(5, "二級節點1.3", 2),
            new Menu(6, "一級節點2", 1),
            new Menu(7, "二級節點2.1", 6),
            new Menu(8, "二級節點2.2", 6),
            new Menu(9, "三級節點2.1.1", 7),
            new Menu(10, "三節點2.1.2", 7),
            new Menu(11, "一級節點3", 1),
            new Menu(12, "二級節點3.1", 11),
            new Menu(13, "三級節點3.1.1", 12),
            new Menu(14, "四級節點3.1.1.1", 13),
            new Menu(15, "五級節點3.1.1.1.1", 14)
    );
}

由於我不喜歡使用實體類,而且實體類並不具體普適性,所以,將實體類轉成Map;

// 實體類轉Map(自己封裝的方法)
List<Map<String, Object>> maps = ListUtils.toMaps(menus);
System.out.println(maps);
[{name=根節點, childList=null, id=1, parentId=0}, {name=一級節點1, childList=null, id=2, parentId=1}, {name=二級節點1.1, childList=null, id=3, parentId=2}, {name=二級節點1.2, childList=null, id=4, parentId=2}, {name=二級節點1.3, childList=null, id=5, parentId=2}, {name=一級節點2, childList=null, id=6, parentId=1}, {name=二級節點2.1, childList=null, id=7, parentId=6}, {name=二級節點2.2, childList=null, id=8, parentId=6}, {name=三級節點2.1.1, childList=null, id=9, parentId=7}, {name=三節點2.1.2, childList=null, id=10, parentId=7}, {name=一級節點3, childList=null, id=11, parentId=1}, {name=二級節點3.1, childList=null, id=12, parentId=11}, {name=三級節點3.1.1, childList=null, id=13, parentId=12}, {name=四級節點3.1.1.1, childList=null, id=14, parentId=13}, {name=五級節點3.1.1.1.1, childList=null, id=15, parentId=14}]

現在,我們該如何將這些資料庫拿到的資料,逐級進行巢狀管理呢? 

3.解決方案

通過遞迴實現(所謂遞迴,就是自己呼叫自己的意思)。

方式一:java8

第一步:遞迴函式實現;

/*
 * 遞迴查詢子節點
 * @description:
 * @param: root 根節點(指定節點)
 * @param: all 所有節點
 * @return: java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
 * 獲取指定節點下屬的所有子節點
 */
private static List<Map<String, Object>> getChildrens(Map<String, Object> root, List<Map<String, Object>> all) {
    // filter()返回子節點
    // peek()往子節點當中塞它下屬的節點,通過自己調自己的方式完成遞迴呼叫,直至沒有下級為止
    return all.stream().filter(m -> Objects.equals(m.get("parentId"), root.get("id"))).peek(
            (m) -> m.put("childList", (getChildrens(m, all)))
    ).collect(Collectors.toList());
}

第二步:確定根節點並呼叫。

// filter()拿到根節點
// peek()獲取根節點下屬的子節點
List<Map<String, Object>> collect = maps.stream().filter(m -> m.get("parentId").equals("0")).peek(
    (m) -> m.put("childList", getChildrens(m, maps))
).collect(Collectors.toList());
System.out.println(collect);
[{name=根節點, childList=[{name=一級節點1, childList=[{name=二級節點1.1, childList=[], id=3, parentId=2}, {name=二級節點1.2, childList=[], id=4, parentId=2}, {name=二級節點1.3, childList=[], id=5, parentId=2}], id=2, parentId=1}, {name=一級節點2, childList=[{name=二級節點2.1, childList=[{name=三級節點2.1.1, childList=[], id=9, parentId=7}, {name=三節點2.1.2, childList=[], id=10, parentId=7}], id=7, parentId=6}, {name=二級節點2.2, childList=[], id=8, parentId=6}], id=6, parentId=1}, {name=一級節點3, childList=[{name=二級節點3.1, childList=[{name=三級節點3.1.1, childList=[{name=四級節點3.1.1.1, childList=[{name=五級節點3.1.1.1.1, childList=[], id=15, parentId=14}], id=14, parentId=13}], id=13, parentId=12}], id=12, parentId=11}], id=11, parentId=1}], id=1, parentId=0}]

方式二:java7及以下

第一步:遞迴函式實現;

/*
 * 樹形巢狀結構
 * @description:
 * @param: disorder 未排序的資料
 * @param: pId 父級ID
 * @param: idName id欄位名
 * @param: pidName 父級id欄位名
 * @return: java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
 * 排序後的樹形結構
 */
public static List<Map<String, Object>> treeSort(@NotNull List<Map<String, Object>> disorder, Long pId, String idName, String pidName) {
    List<Map<String, Object>> children = new ArrayList<>();
    // 獲取直系子類
    // java8一行搞定
    // List<Map<String, Object>> children = disorder.stream().filter(m -> Long.parseLong((String) m.get(pidName)) == pId).collect(Collectors.toList());
    for (Map<String, Object> map : disorder) {
        if (Long.parseLong((String) map.get(pidName)) == pId) {
            children.add(map);
        }
    }
    
    // 沒有子節點的話,返回[]
    if(children.isEmpty()) return children;

    // 對子類進行Map<String, Object>排序
    // java8一行搞定氣泡排序
    // children.sort(Comparator.comparing(m -> (Long.parseLong((String) m.get(idName)))));
    boolean flag;
    // 氣泡排序(小數在前,大數在後)
    for (int i = 0; i < children.size() - 1; i++) {// 冒泡趟數,i-1趟
        flag = false;
        for (int j = 1; j < children.size() - i; j++) {
            Map<String, Object> temp;
            // 當小的在後面時,二者換位
            if (Long.parseLong((String) children.get(j - 1).get(idName)) > Long.parseLong((String) children.get(j).get(idName))) {
                temp = children.get(j - 1);
                children.set(j - 1, children.get(j));
                children.set(j, temp);
                flag = true;
            }
        }

        if (!flag) {// 如果沒有發生交換,則退出迴圈
            break;
        }
    }

    //獲取非子類
    // java8一行搞定
    // List<Map<String, Object>> successor = disorder.stream().filter(x -> Long.parseLong((String) x.get(pidName)) != pId).collect(Collectors.toList());
    List<Map<String, Object>> successor = new ArrayList<>();
    // 獲取直系子類
    for (Map<String, Object> map : disorder) {
        if (Long.parseLong((String) map.get(pidName)) != pId) {
            successor.add(map);
        }
    }

    // 按照上下級排好序
    // 迭代直系子類
    for (Map<String, Object> m : children) {
        // 獲取子類的子類(自己調自己)
        List<Map<String, Object>> subList = treeSort(successor, Long.parseLong((String) m.get(idName)), idName, pidName);
        m.put("childList", subList);
    }

    return children;
}

第二步:確定根節點並呼叫。

// 根節點為0
List<Map<String, Object>> mapList = treeSort(maps, 0L, "id", "parentId");
System.out.println(mapList);
[{name=根節點, childList=[{name=一級節點1, childList=[{name=二級節點1.1, childList=[], id=3, parentId=2}, {name=二級節點1.2, childList=[], id=4, parentId=2}, {name=二級節點1.3, childList=[], id=5, parentId=2}], id=2, parentId=1}, {name=一級節點2, childList=[{name=二級節點2.1, childList=[{name=三級節點2.1.1, childList=[], id=9, parentId=7}, {name=三節點2.1.2, childList=[], id=10, parentId=7}], id=7, parentId=6}, {name=二級節點2.2, childList=[], id=8, parentId=6}], id=6, parentId=1}, {name=一級節點3, childList=[{name=二級節點3.1, childList=[{name=三級節點3.1.1, childList=[{name=四級節點3.1.1.1, childList=[{name=五級節點3.1.1.1.1, childList=[], id=15, parentId=14}], id=14, parentId=13}], id=13, parentId=12}], id=12, parentId=11}], id=11, parentId=1}], id=1, parentId=0}]

4.效果展示

為了方便檢視,對其進行格式化。

檢視程式碼
[{
		name = 根節點,
		childList = [{
				name = 一級節點1,
				childList = [{
						name = 二級節點1.1,
						childList = [],
						id = 3,
						parentId = 2
					}, {
						name = 二級節點1.2,
						childList = [],
						id = 4,
						parentId = 2
					}, {
						name = 二級節點1.3,
						childList = [],
						id = 5,
						parentId = 2
					}
				],
				id = 2,
				parentId = 1
			}, {
				name = 一級節點2,
				childList = [{
						name = 二級節點2.1,
						childList = [{
								name = 三級節點2.1.1,
								childList = [],
								id = 9,
								parentId = 7
							}, {
								name = 三節點2.1.2,
								childList = [],
								id = 10,
								parentId = 7
							}
						],
						id = 7,
						parentId = 6
					}, {
						name = 二級節點2.2,
						childList = [],
						id = 8,
						parentId = 6
					}
				],
				id = 6,
				parentId = 1
			}, {
				name = 一級節點3,
				childList = [{
						name = 二級節點3.1,
						childList = [{
								name = 三級節點3.1.1,
								childList = [{
										name = 四級節點3.1.1.1,
										childList = [{
												name = 五級節點3.1.1.1.1,
												childList = [],
												id = 15,
												parentId = 14
											}
										],
										id = 14,
										parentId = 13
									}
								],
								id = 13,
								parentId = 12
							}
						],
						id = 12,
						parentId = 11
					}
				],
				id = 11,
				parentId = 1
			}
		],
		id = 1,
		parentId = 0
	}
]

如果有必要,可以對其進行進一步處理。

// 轉json
System.out.println(JSON.toJSON(collect));
[{"name":"根節點","childList":[{"name":"一級節點1","childList":[{"name":"二級節點1.1","childList":[],"id":"3","parentId":"2"},{"name":"二級節點1.2","childList":[],"id":"4","parentId":"2"},{"name":"二級節點1.3","childList":[],"id":"5","parentId":"2"}],"id":"2","parentId":"1"},{"name":"一級節點2","childList":[{"name":"二級節點2.1","childList":[{"name":"三級節點2.1.1","childList":[],"id":"9","parentId":"7"},{"name":"三節點2.1.2","childList":[],"id":"10","parentId":"7"}],"id":"7","parentId":"6"},{"name":"二級節點2.2","childList":[],"id":"8","parentId":"6"}],"id":"6","parentId":"1"},{"name":"一級節點3","childList":[{"name":"二級節點3.1","childList":[{"name":"三級節點3.1.1","childList":[{"name":"四級節點3.1.1.1","childList":[{"name":"五級節點3.1.1.1.1","childList":[],"id":"15","parentId":"14"}],"id":"14","parentId":"13"}],"id":"13","parentId":"12"}],"id":"12","parentId":"11"}],"id":"11","parentId":"1"}],"id":"1","parentId":"0"}]

格式化

檢視程式碼
[{
	"name": "根節點",
	"childList": [{
		"name": "一級節點1",
		"childList": [{
			"name": "二級節點1.1",
			"childList": [],
			"id": "3",
			"parentId": "2"
		},
		{
			"name": "二級節點1.2",
			"childList": [],
			"id": "4",
			"parentId": "2"
		},
		{
			"name": "二級節點1.3",
			"childList": [],
			"id": "5",
			"parentId": "2"
		}],
		"id": "2",
		"parentId": "1"
	},
	{
		"name": "一級節點2",
		"childList": [{
			"name": "二級節點2.1",
			"childList": [{
				"name": "三級節點2.1.1",
				"childList": [],
				"id": "9",
				"parentId": "7"
			},
			{
				"name": "三節點2.1.2",
				"childList": [],
				"id": "10",
				"parentId": "7"
			}],
			"id": "7",
			"parentId": "6"
		},
		{
			"name": "二級節點2.2",
			"childList": [],
			"id": "8",
			"parentId": "6"
		}],
		"id": "6",
		"parentId": "1"
	},
	{
		"name": "一級節點3",
		"childList": [{
			"name": "二級節點3.1",
			"childList": [{
				"name": "三級節點3.1.1",
				"childList": [{
					"name": "四級節點3.1.1.1",
					"childList": [{
						"name": "五級節點3.1.1.1.1",
						"childList": [],
						"id": "15",
						"parentId": "14"
					}],
					"id": "14",
					"parentId": "13"
				}],
				"id": "13",
				"parentId": "12"
			}],
			"id": "12",
			"parentId": "11"
		}],
		"id": "11",
		"parentId": "1"
	}],
	"id": "1",
	"parentId": "0"
}]

我們可以看到:樹形結構已經實現。

寫在最後

  哪位大佬如若發現文章存在紕漏之處或需要補充更多內容,歡迎留言!!!

 相關推薦: