7-30目录树 C++题解 思路清晰

题目链接

在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()。

  1. 以 ‘\’ 结尾,提取出的是 目录名
  2. 以 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 遍历输出即可,只是要注意两点:

  1. 目录在前,文件在后,字典升序
  2. 记录深度,用于输出前导空格

代码如下:

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;
}

总结

这么看下来,好像也不难。可是我第一次做的时候蒙了半天,遇见非常规题就不知所措,其实是自己把自己吓住了,尤其是近几年的考试。希望今年稳住心态,奥利给!

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值