上机编程[目录树收缩显示]学习交流

1.    题目

今天学习2022-11-25专业级第二题。

在一个目录树中(假设都是目录),过深的目录路径不容易展示,为了提升用户体验,需要对目录进行收缩展示,求收缩后某一深度的目录个数。
如图所示,原目录树及收缩后示意:

1.  root                    root

2.  ├ B                     ├ B/C/E

3.  │ └ C                   │ ├ N

4.  │   └ E                 │ └ M

5.  │     ├ N               └ F

6.  │     └ M                 ├ H

7.  └ F                       └ X/i

8.    ├ H

9.    └ X

10.     └ i

收缩规则:

·         若某目录仅有一个子目录,则把这个子目录收缩到其父目录,展示为一个新目录。如图所示,i 收缩到 X 变成新的目录 X/i。

·         所有符合上述条件的均需收缩,收缩后符合上述条件的继续收缩,直到无法收缩。如图所示,目录B、C、E收缩为新的目录 B/C/E

现给定一个原目录树 orgTree,请按照收缩规则展示为一个新目录树,求新目录树中深度值为 depth(根节点深度为 1)的节点个数。

如图所示,收缩后深度为 2 的节点有 2 个(B/C/E、F),深度为 3 的节点有 4 个(N、M、H、X/i)。

输入

一个整数 num,表示父子节点对的数量,1 <= num <= 300
接下来 num 行表示 orgTree,每行一个父子节点对,格式为父节点 子节点,节点名称仅含字母或数字,长度 [1,10]
最后一行一个整数 depth,1 <= depth <= 300

树只有一个根,首个节点对的父节点为根。
树上各节点名称是全局唯一的。
每个节点下的子节点不超过10个。

输出

一个整数,表示收缩后深度为 depth 的节点个数

样例

输入样例 1

9

root B

root F

C E

B C

E N

F H

F X

E M

X i

3

输出样例 1

4

提示样例 1

输入数据表示的原目录树,及收缩示意如题面图示,深度为 3 的节点有 4 个(N、M、H、X/i)

输入样例 2

3

1 B123456789

B123456789 c

1 b123456789

3

输出样例 2

0

提示样例 2

原目录树收缩后,不存在深度为 3 的节点,因此返回 0

输入样例 3

4

A B

B C

C D

D E

1

输出样例 3

1

提示样例 3

收缩显示为一个节点 A/B/C/D/E,深度 1 的节点数为 1

 

2.    题目分析

题目理解:

现给出一棵原始的目录树(只给了“父节点-子节点”对),按规则收缩(也可以称作为合并或者折叠)后,求深度为depth的节点个数:

l  规则1: 如果当前目录的子目录数为1,则向上收缩,直到无法收缩。

l  规则2: 根节点为orgTree[0][0],唯一且深度为1。

注意,树上各节点名称是全局唯一的。

思路解析:

如果没有收缩的需求,那么只需要以深度优先或者广度优先的方式遍历整棵树,就可以得到结果。

要实现收缩的需求,有两个思路:

l  可以先收缩树节点,再遍历统计

例如,如果已经构建出树了,可以这样递归实现收缩:

func dfsMerge(node *treeNode) {

    parent := node

    for ; len(parent.children) == 1; {

        // B-C 合并为 B/C

        child := parent.children[0]

        parent.name = parent.name + "/" + child.name

        parent.children = child.children

    }

    for i, child := range node.children {

        dfsMerge(child)

    }

}

l  也可以直接遍历统计,遇到需要收缩的节点不计算这一层的深度即可

下面看看大家的具体代码实现。

 

总结

1)        本题解法总结

同样是BFS,员工5的GO语言解法遍历到深度为depth后就停止搜索,效率比较高;C++的解法需要遍历所有节点,效率要差一些。

员工3的JAVA代码和员工4的PYTHON代码都是建立树之后进行DFS遍历,但存在差异:

l  员工3的JAVA解法定义了一个单独的类TreeNode表示节点,而PYTHON解法用字典的key作父节点、字典的value是一个数组用来存储子节点。

l  员工3的JAVA解法传入的node参数是父节点,而PYTHON解法的nodes是子节点。

当树节点需要存储的属性比较少时,可以直接用数组等数据结构;而当需要存储的属性比较多时,建议定义一个单独的结构体TreeNode,这样代码的可维护性也比较好。

2)        树的深度计算和遍历

 

#########

dfs

 

#################
dfs 算法一步到位
#################

package main

import "fmt"

type dirNode struct {
    name     string
    children []*dirNode
}

func buildTree(parent, child string, nodes map[string]*dirNode) {
    if nodes[parent] == nil {
        nodes[parent] = &dirNode{name: parent}
    }
    if nodes[child] == nil {
        nodes[child] = &dirNode{name: child}
    }
    nodes[parent].children = append(nodes[parent].children, nodes[child])
}

func getTargetNum(node *dirNode, depth, target int) int {
    for len(node.children) == 1 {
        node = node.children[0]
    }
    if depth == target {
        return 1
    }
    result := 0
    for i := 0; i < len(node.children); i++ {
        result += getTargetNum(node.children[i], depth+1, target)
    }
    return result
}

func getNodesNum(orgTree [][]string, depth int) int {
    nodes := make(map[string]*dirNode)
    for _, tree := range orgTree {
        buildTree(tree[0], tree[1], nodes)
    }
    res := getTargetNum(nodes["root"], 1, depth)
    return res
}

func main() {
    res := getNodesNum([][]string{
        {"root", "B"},
        {"root", "F"},
        {"C", "E"},
        {"B", "C"},
        {"E", "N"},
        {"F", "N"},
        {"F", "X"},
        {"E", "M"},
        {"X", "i"},
    }, 3)
    fmt.Println(res)
}
View Code

 

#########

bfs

 

package main

import "fmt"

type dirNode struct {
    name     string
    children []*dirNode
}

func buildTree(parent, child string, nodes map[string]*dirNode) {
    if nodes[parent] == nil {
        nodes[parent] = &dirNode{name: parent}
    }
    if nodes[child] == nil {
        nodes[child] = &dirNode{name: child}
    }
    nodes[parent].children = append(nodes[parent].children, nodes[child])
}

func getTargetNum(node *dirNode, depth, target int) int {
    for len(node.children) == 1 {
        node = node.children[0]
    }
    if depth == target {
        return 1
    }
    result := 0
    for i := 0; i < len(node.children); i++ {
        result += getTargetNum(node.children[i], depth+1, target)
    }
    return result
}

func flodDir(nodes *dirNode) *dirNode {
    for _, child := range nodes.children {
        flodDir(child)
    }
    if len(nodes.children) == 1 {
        nodes.name = nodes.name + "/" + nodes.children[0].name
        nodes.children = nodes.children[0].children
    }
    if nodes.children == nil {
        return nodes
    }
    return nil
}

func getTargetNum2(nodes *dirNode, depth int) int {
    count := 0

    queue := []*dirNode{nodes}
    level := 1
    for len(queue) != 0 {
        queueLen := len(queue)
        if level == depth {
            count = queueLen
        }
        for i := 0; i < queueLen; i++ {
            cur := queue[0]
            queue = queue[1:]
            // 已经遍历到最后一层了
            if cur.children == nil {
                return count
            }
            queue = append(queue, cur.children...)
        }
        level++
    }
    return count
}

func getNodesNum(orgTree [][]string, depth int) int {
    nodes := make(map[string]*dirNode)
    for _, tree := range orgTree {
        buildTree(tree[0], tree[1], nodes)
    }
    //res := getTargetNum(nodes["root"], 1, depth)
    flodDir(nodes["root"])
    res := getTargetNum2(nodes["root"], depth)
    return res
}

func main() {
    res := getNodesNum([][]string{
        {"root", "B"},
        {"root", "F"},
        {"C", "E"},
        {"B", "C"},
        {"E", "N"},
        {"F", "N"},
        {"F", "X"},
        {"E", "M"},
        {"X", "i"},
    }, 3)
    fmt.Println(res)
}
View Code

 

 感受

1. 建多叉树 使用map[string]*node建更有效

2. dfs( 先序,先考虑条件)

3. bfs中  flodDir 递归的话,可以先递归,再根据条件逆序判断

4. bfs 模版理解本质,queue, 层序遍历



 

posted @ 2023-12-08 18:17  易先讯  阅读(82)  评论(0编辑  收藏  举报