在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
分析:首先看到这道题,很容易想到前面一道相似的题目:7-27家谱处理。
简单回顾一下:7-27家谱处理给出 目录式的结构,要求判断结点之间的相对关系。而本题给出 文件路径,要求输出 目录式的结构,很像是 7-27 的逆问题?
程序 = 数据结构 + 算法
数据结构的设计
首先,子结点到父节点 是 一对一 的,而 父节点到子结点 是 一对多 的。
7-27 中,只需要查询结点之间的相对关系,则使用 哈希map 保存下每个结点的父结点信息,迭代查询即可。是从下至上的,一对一的,所以可以只使用map记录父亲。
而本题中,需要从根节点向下输出整个目录,是从上至下的,一对多的,故需要使用结构体来存储所有的孩子 (①)。
题目包含了 “目录”、“文件”两种结构,且存在同名,所以需要标记(②),以及本身的文件/目录名(③)。
综上,树的结点数据结构为:
struct node {
string name;
int isCata; // 目录文件标记
vector<node*> child; // 孩子指针
};
注意在定义树的结构时,保存的是孩子结点的指针。
算法设计
首先,整个题目的解决框架为:先建树,再输出树
目录树
|
—— 1.建树
|
—— 2.输出
1.建树
建树过程,首先顺序扫描字符串,把字符串中的 单词 给提取出来,结束的标志位 ‘\’ 或 字符串尾 end()。
- 以 ‘\’ 结尾,提取出的是 目录名
- 以 end() 结束,提取出的是 文件名
设置一个变量 curRoot,在处理字符串时记录下 当前父目录 的指针,每提取出一个名称,就将其插入到 当前父目录,然后更新 当前父目录。
a\d\a
↑ 遍历之前,curRoot = root
a\d\a
↑ 提取出 目录 a。 不存在 a,新建 a,curRoot = a
a\d\a
↑ 提取出 目录 d。 不存在 d,建立 d,curRoot = d
a\d\a
↑ 提取出 文件 a。 不存在 a,新建 a,curRoot = d
综上所述,建树的过程总结如下:
1.建树:扫描字符串
|
—— 1.1 str[i] == '\' : 提取出 目录名,切换当前父目录
—— 1.1.1 目录存在,直接切换当前父目录
—— 1.1.2 目录不存在,新建,切换
—— 1.2 i == str.end() : 提取出 文件名,插入当前父目录
—— 1.3 其他 :累加字符
代码如下:
// 建立根节点
node* root = new node;
root->name = "root";
root->isCata = 1;
string tmp,str;
node* curRoot;
for(int j = 0; j < n; ++j) {
// 每一个新的路径,都将根设为 root
curRoot = root;
getline(cin,str);
for(int i = 0; i <= str.size(); ++i) {
if(str[i] == '\\') { // 情况 1. 是目录 : 切换当前目录,
// 在当前父目录中寻找,看是否存在
int flag = 0;
for(int k = 0; k < curRoot->child.size(); ++k) {
// 1.1 有该目录
if(curRoot->child[k]->name == tmp && curRoot->child[k]->isCata == 1) {
// 则切换当前目录
curRoot = curRoot->child[k];
flag = 1;
break;
}
}
// 1.2 没有该目录则创建一个
if(!flag) {
// 创建结点
node* newnode = new node;
newnode->name = tmp;
newnode->isCata = 1;
// 加入父目录
curRoot->child.push_back(newnode) ;
// 切换当前目录
curRoot = newnode;
}
// 单词清零
tmp.clear();
// 情况 2. 是文件
}else if(i == str.size()) {
if(!tmp.empty()) { // 到达最后,而单词不空,说明是文件
// 将文件加入到父节点中
node* newnode = new node;
newnode->name = tmp;
newnode->isCata = 0;
curRoot->child.push_back(newnode) ;
}
tmp.clear();
} else { // 情况 3. 累加单词字母
tmp += str[i];
}
}
}
2.输出
输出就比较简单了,dfs 遍历输出即可,只是要注意两点:
- 目录在前,文件在后,字典升序
- 记录深度,用于输出前导空格
代码如下:
bool cmp(node* a, node* b) {
if(a->isCata != b->isCata) return a->isCata > b->isCata;
else return a->name < b->name;
}
void dfs(node* root,int level) {
if(root == NULL) return ;
// 先输出自己
for(int i = 0; i < level; ++i) printf(" ");
printf("%s\n",root->name.c_str()) ;
// 排序所有孩子 : 目录在前,文件在后,字典升序
sort(root->child.begin(),root->child.end(),cmp);
// 向下递归
for(int i = 0; i < root->child.size(); ++i)
dfs(root->child[i],level+1);
}
3.合并
将上述内容总结,可得:
目录树
|
——1.建树:扫描字符串
|
—— 1.1 str[i] == '\' : 提取出 目录名,切换当前父目录
—— 1.1.1 目录存在,直接切换当前父目录
—— 1.1.2 目录不存在,新建,切换
—— 1.2 i == str.end() : 提取出 文件名,插入当前父目录
—— 1.3 其他 :累加字符
—— 2.输出
|
—— 2.1 自定义排序
—— 2.2 前导空格
AC代码如下:
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<set>
#include<map>
#define MAXN 100010
using namespace std;
struct node {
string name;
int isCata; // 目录文件标记
vector<node*> child; // 孩子指针
};
bool cmp(node* a, node* b) {
if(a->isCata != b->isCata) return a->isCata > b->isCata;
else return a->name < b->name;
}
void dfs(node* root,int level) {
if(root == NULL) return ;
// 先输出自己
for(int i = 0; i < level; ++i) printf(" ");
printf("%s\n",root->name.c_str()) ;
// 排序所有孩子 : 目录在前,文件在后,字典升序
sort(root->child.begin(),root->child.end(),cmp);
// 向下递归
for(int i = 0; i < root->child.size(); ++i)
dfs(root->child[i],level+1);
}
int n;
int main() {
scanf("%d",&n);
getchar();
// 建立根节点
node* root = new node;
root->name = "root";
root->isCata = 1;
string tmp,str;
node* curRoot;
for(int j = 0; j < n; ++j) {
// 每一个新的路径,都将根设为 root
curRoot = root;
getline(cin,str);
for(int i = 0; i <= str.size(); ++i) {
if(str[i] == '\\') { // 情况 1. 是目录 : 切换当前目录,
// 在当前父目录中寻找,看是否存在
int flag = 0;
for(int k = 0; k < curRoot->child.size(); ++k) {
// 1.1 有该目录
if(curRoot->child[k]->name == tmp && curRoot->child[k]->isCata == 1) {
// 则切换当前目录
curRoot = curRoot->child[k];
flag = 1;
break;
}
}
// 1.2 没有该目录则创建一个
if(!flag) {
// 创建结点
node* newnode = new node;
newnode->name = tmp;
newnode->isCata = 1;
// 加入父目录
curRoot->child.push_back(newnode) ;
// 切换当前目录
curRoot = newnode;
}
// 单词清零
tmp.clear();
// 情况 2. 是文件
}else if(i == str.size()) {
if(!tmp.empty()) { // 到达最后,而单词不空,说明是文件
// 将文件加入到父节点中
node* newnode = new node;
newnode->name = tmp;
newnode->isCata = 0;
curRoot->child.push_back(newnode) ;
}
tmp.clear();
} else { // 情况 3. 累加单词字母
tmp += str[i];
}
}
}
// 输出过程
dfs(root,0);
return 0;
}
总结
这么看下来,好像也不难。可是我第一次做的时候蒙了半天,遇见非常规题就不知所措,其实是自己把自己吓住了,尤其是近几年的考试。希望今年稳住心态,奥利给!