题目要求
在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
解题思路
-
定义数据结构:
-
使用一个树形结构
TreeNode
来表示目录树中的每个节点。 -
每个节点包含:
-
name
: 节点名称(文件名或目录名)。 -
isDirectory
: 是否为目录。 -
children
: 子节点的映射,使用map
以字典序存储,键是名称,值是子节点。
-
-
-
解析输入并建立树:
-
读取输入的文件和目录路径,总数为
N
。 -
对每一个路径字符串:
-
先判断是否是目录(最后一个字符是否是
\
)。 -
调用
splitPath
函数,将路径字符串按\
分割,得到路径的各级目录和文件。 -
调用
insertPath
函数,将这些路径段依次插入到树中。-
从根节点开始逐级往下遍历,如果对应的目录或文件不存在,则创建新的节点并连接到父节点。
-
-
-
-
递归打印目录树:
-
从根节点开始,使用深度优先搜索(DFS)递归遍历:
-
先遍历所有子目录(按照字典序)。
-
再遍历所有文件(按照字典序)。
-
-
每下一级,缩进增加两个空格。
-
使用
printTree
完成递归输出。
-
设计分析
-
时间复杂度:
-
插入路径:每个路径最多有
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)
。
-
-
空间复杂度:
-
树的存储空间:
O(N * L)
,所有路径信息存储在树中。 -
递归栈空间:
O(L)
,最大深度的路径决定栈的深度。 -
总空间复杂度是
O(N * L)
。
-
-
边界情况:
-
空路径:不会出现,因为
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;
}
个人总结
-
树形结构是解决路径组织问题的优选方案:
-
这道题目本质是路径树的构建,通过树结构存储每个路径的节点,实现多级目录的递归展示。
-
-
合理的数据结构选择优化查询效率:
-
使用
map
存储子节点能天然保持字典序,简化了排序逻辑。 -
直接在插入时管理是否是目录或文件,避免后续多余的判断。
-
-
递归打印清晰直观:
-
递归是最自然的目录遍历方式,通过缩进来表示深度,代码可读性高且简洁。
-
-
分离逻辑简化复杂度:
-
splitPath
、insertPath
和printTree
逻辑清晰,模块化分离降低了复杂度,易于维护。
-