1-10 目录树

题目要求

在ZIP归档文件中,保留着所有压缩文件和目录的相对路径和名称。当使用WinZIP等GUI软件打开ZIP归档文件时,可以从这些信息中重建目录的树状结构。请编写程序实现目录的树状结构的重建工作。

输入格式:

输入首先给出正整数N(≤104),表示ZIP归档文件中的文件和目录的数量。随后N行,每行有如下格式的文件或目录的相对路径和名称(每行不超过260个字符):

  • 路径和名称中的字符仅包括英文字母(区分大小写);
  • 符号“\”仅作为路径分隔符出现;
  • 目录以符号“\”结束;
  • 不存在重复的输入项目;
  • 整个输入大小不超过2MB。

输出格式:

假设所有的路径都相对于root目录。从root目录开始,在输出时每个目录首先输出自己的名字,然后以字典序输出所有子目录,然后以字典序输出所有文件。注意,在输出时,应根据目录的相对关系使用空格进行缩进,每级目录或文件比上一级多缩进2个空格。

输入样例:

7
b
c\
ab\cd
a\bc
ab\d
a\d\a
a\d\z\

输出样例:

root
  a
    d
      z
      a
    bc
  ab
    cd
    d
  c
  b

解题思路

  1. 定义数据结构:

    • 使用一个树形结构 TreeNode 来表示目录树中的每个节点。

    • 每个节点包含:

      • name: 节点名称(文件名或目录名)。

      • isDirectory: 是否为目录。

      • children: 子节点的映射,使用 map 以字典序存储,键是名称,值是子节点。

  2. 解析输入并建立树:

    • 读取输入的文件和目录路径,总数为 N

    • 对每一个路径字符串:

      • 先判断是否是目录(最后一个字符是否是 \)。

      • 调用 splitPath 函数,将路径字符串按 \ 分割,得到路径的各级目录和文件。

      • 调用 insertPath 函数,将这些路径段依次插入到树中。

        • 从根节点开始逐级往下遍历,如果对应的目录或文件不存在,则创建新的节点并连接到父节点。

  3. 递归打印目录树:

    • 从根节点开始,使用深度优先搜索(DFS)递归遍历:

      • 先遍历所有子目录(按照字典序)。

      • 再遍历所有文件(按照字典序)。

    • 每下一级,缩进增加两个空格。

    • 使用 printTree 完成递归输出。

设计分析

  1. 时间复杂度:

    • 插入路径:每个路径最多有 260 个字符,分割后最大路径深度是 260。因此插入的时间复杂度是 O(L),其中 L 是路径的深度。

    • 遍历所有路径:总共有 N 个路径,时间复杂度是 O(N * L)

    • 排序:对每个节点的子节点进行排序,假设每个节点平均有 k 个子节点,排序是 O(k log k)

    • 总时间复杂度是 O(N * L + K log K),其中 K 是所有节点的总数。

    • 最坏情况下,K 近似等于 N,所以复杂度为 O(N log N)

  2. 空间复杂度:

    • 树的存储空间:O(N * L),所有路径信息存储在树中。

    • 递归栈空间:O(L),最大深度的路径决定栈的深度。

    • 总空间复杂度是 O(N * L)

  3. 边界情况:

    • 空路径:不会出现,因为 N >= 1

    • 只有文件或只有目录:都能正常输出。

    • 单级目录下有多个文件或子目录:会自动按字典序排列。

运行结果

完整代码

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <string>
using namespace std;

struct TreeNode {
    string name;
    bool isDirectory;
    map<string, TreeNode*> children;

    TreeNode(string n, bool isDir) : name(n), isDirectory(isDir) {}
};

void insertPath(TreeNode* root, const vector<string>& parts, bool isDirectory) {
    TreeNode* current = root;
    for (size_t i = 0; i < parts.size(); ++i) {
        const string& part = parts[i];
        bool isDir = (i != parts.size() - 1) || isDirectory;
        if (current->children.find(part) == current->children.end()) {
            current->children[part] = new TreeNode(part, isDir);
        }
        current = current->children[part];
        current->isDirectory = isDir;
    }
}

void printTree(TreeNode* node, int indent) {
    string indentation(indent, ' ');
    cout << indentation << node->name << endl;
    
    vector<TreeNode*> dirs, files;
    for (auto& pair : node->children) {
        if (pair.second->isDirectory) {
            dirs.push_back(pair.second);
        } else {
            files.push_back(pair.second);
        }
    }
    
    sort(dirs.begin(), dirs.end(), [](TreeNode* a, TreeNode* b) {
        return a->name < b->name;
    });
    sort(files.begin(), files.end(), [](TreeNode* a, TreeNode* b) {
        return a->name < b->name;
    });
    
    for (auto dir : dirs) {
        printTree(dir, indent + 2);
    }
    for (auto file : files) {
        printTree(file, indent + 2);
    }
}

vector<string> splitPath(const string& path) {
    vector<string> parts;
    size_t start = 0;
    size_t end = path.find('\\');
    while (end != string::npos) {
        parts.push_back(path.substr(start, end - start));
        start = end + 1;
        end = path.find('\\', start);
    }
    if (start < path.size()) {
        parts.push_back(path.substr(start));
    }
    return parts;
}

int main() {
    int N;
    cin >> N;
    cin.ignore(); // 忽略换行符
    
    TreeNode* root = new TreeNode("root", true);
    
    for (int i = 0; i < N; ++i) {
        string line;
        getline(cin, line);
        bool isDirectory = (line.back() == '\\');
        vector<string> parts = splitPath(line);
        insertPath(root, parts, isDirectory);
    }
    
    printTree(root, 0);
    
    return 0;
}

个人总结

  1. 树形结构是解决路径组织问题的优选方案

    • 这道题目本质是路径树的构建,通过树结构存储每个路径的节点,实现多级目录的递归展示。

  2. 合理的数据结构选择优化查询效率

    • 使用 map 存储子节点能天然保持字典序,简化了排序逻辑。

    • 直接在插入时管理是否是目录或文件,避免后续多余的判断。

  3. 递归打印清晰直观

    • 递归是最自然的目录遍历方式,通过缩进来表示深度,代码可读性高且简洁。

  4. 分离逻辑简化复杂度

    • splitPathinsertPathprintTree 逻辑清晰,模块化分离降低了复杂度,易于维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值