Week9 作业——A - 咕咕东的目录管理器

题目

咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 —— 这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!
初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root。
目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。在这里插入图片描述

现在咕咕东可以在命令行下执行以下表格中描述的命令:

命令
类型
实现
说明

MKDIR s
操作
在当前目录下创建一个子目录 s,s 是一个字符串
创建成功输出 “OK”;若当前目录下已有该子目录则输出 “ERR”

RM s
操作
在当前目录下删除子目录 s,s 是一个字符串
删除成功输出 “OK”;若当前目录下该子目录不存在则输出 “ERR”

CD s
操作
进入一个子目录 s,s 是一个字符串(执行后,当前目录可能会改变)
进入成功输出 “OK”;若当前目录下该子目录不存在则输出 “ERR”
特殊地,若 s 等于 “…” 则表示返回上级目录,同理,返回成功输出 “OK”,返回失败(当前目录已是根目录没有上级目录)则输出 “ERR”

SZ
询问
输出当前目录的大小
也即输出 1+当前目录的子目录数

LS
询问
输出多行表示当前目录的 “直接子目录” 名
若没有子目录,则输出 “EMPTY”;若子目录数属于 [1,10] 则全部输出;若子目录数大于 10,则输出前 5 个,再输出一行 “…”,输出后 5 个。

TREE
询问
输出多行表示以当前目录为根的子树的前序遍历结果
若没有后代目录,则输出 “EMPTY”;若后代目录数+1(当前目录)属于 [1,10] 则全部输出;若后代目录数+1(当前目录)大于 10,则输出前 5 个,再输出一行 “…”,输出后 5 个。若目录结构如上图,当前目录为 “root” 执行结果如下,
在这里插入图片描述
UNDO
特殊
撤销操作
撤销最近一个 “成功执行” 的操作(即MKDIR或RM或CD)的影响,撤销成功输出 “OK” 失败或者没有操作用于撤销则输出 “ERR”

输入

输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
面对数据范围你要思考的是他们代表的 “命令” 执行的最大可接受复杂度,只有这样你才能知道你需要设计的是怎样复杂度的系统。

输出

每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
时空限制
Time limit 6000 ms
Memory limit 1048576 kB

扫描二维码关注公众号,回复: 11960937 查看本文章

样例输入

1
22
MKDIR dira
CD dirb
CD dira
MKDIR a
MKDIR b
MKDIR c
CD ..
MKDIR dirb
CD dirb
MKDIR x
CD ..
MKDIR dirc
CD dirc
MKDIR y
CD ..
SZ
LS
TREE
RM dira
TREE
UNDO
TREE

样例输出

OK
ERR
OK
OK
O


K
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
9
dira
dirb
dirc
root
dira
a
b
c
dirb
x
dirc
y
OK
root
dirb
x
dirc
y
OK
root
dira
a
b
c
dirb
x
dirc
y

做法

Step0 无从下手 
经验:如果一下子不习惯/无法从整个大局进行考虑,自顶向下地设计整 个程序框架,不妨就从程序的入口开始

Step1.思考封装 
考虑“有什么”和“做什么”。 
比如刚刚我们有一个命令字符串“tmps”,它即代表一条命令。 
考虑到一条命令不单有命令形式,还有命令参数,比如“MKDIR s”,等 会我们肯定还要进行参数的分离,同类信息最好内聚,所以——封装之

Step2.树形结构 
维护一棵目录树是实现这道题的必然需求——如何维护一棵树?
题目要求一个目录能够根据子目录的名字取到它的子目录 
根据键取值,Key-Value ?所以是map ? 
对,如果不要求字典序,用底层是哈希的unordered_map,O(1) 的复 杂度,将绝杀,可惜用不得 
所以要用map<string,目录>,它可以根据key 也就是string 在内部进 行排序。这样每次可以log 级别复杂度取到子目录

Step3.解题框架 
要开始写具体实现的时候,你发现,对于每条指令要有一个执行对象, 那就是当前目录“now”
如此,解题框架就如下:即我们在Directory 结构体里面写各种具体的实 现
同时你发现两个问题,第一,你要设计“返回值”告知某条命令的执行 的结果成功/失败 
第二,你想起来“UNDO”没法封装在某一个Directory 内部,它是隶属于 当前测试数据环境的 
所以,对于执行成功的命令,你还要存起来,以备“UNDO”
回去题面分析一下可以UNDO 的命令有关键 
必须是MKDIR、RM、CD 三种之一 
必须已执行成功 
如果UNDO  MKDIR 的UNDO 就是删掉,那么删谁? 
RM 的UNDO 就是加回去,那么加谁? 
CD 的UNDO 就是退回去,退到哪? 
——需要保存每条指令执行的执行结果,保存到-> struct Command{…}

step4 处理细节

Step5.注入灵魂 
直到要实现TREE 命令的时候,你发现它是最难的 
因为在后代节点数量大于10 时,要开始前序遍历和后序遍历 
并且对复杂度有要求!
TREE的命令条数最多约为1e5,此时整个树最多5000 个节点 
20 * 1e5 * 5000,你发现可能会TLE 
——你需要一个较好的实现
答案是缓存(懒更新),节点数远少于TREE 操作数,指不定还有重复询 问,对于目录相同期间问过的相同问题,理应只有一次是计算过程

代码

#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#include <vector>
#include <map>
using namespace std;

char tmps[20];//辅助输入的字符数组  
struct directory{
    
    
	string name;//当前目录的名字
	map<string,directory*>children;//孩子目录
	directory*parent;
	int subtreeSize;//子树大小
	vector<string>*ten;//缓存0个后代 
	bool updated;//有没有被问过 
	directory(string name,directory*parent){
    
    
		this->name=name;
		this->parent=parent;
		this->subtreeSize=1;
		ten=new vector<string>;
		updated=1;
	} 
	bool addchild(directory*ch){
    
    
		if(children.find(ch->name)!=children.end()){
    
    return 0;}
		children[ch->name]=ch;
		maintain(+ch->subtreeSize);
		return 1;
	} 
	
	directory* getchild(string name){
    
    
		auto it=children.find(name);
		if(it==children.end()){
    
    return NULL;}
		else{
    
    return it->second;}
	}
	
	directory*mkdir(string name){
    
    
		if(children.find(name)!=children.end())
		{
    
    return NULL;}//已经有这个子目录,不创建 
		directory*ch=new directory(name,this);
		children[name]=ch;
		maintain(1);
		return ch; 
	}
		
	directory*rm(string name){
    
    
		auto it= children.find(name);
		if(it==children.end()){
    
    return NULL;}//不存在这个子目录,无法删除
		maintain(-1*it->second->subtreeSize);
		directory*temp=it->second;///本来没有这一步 
		children.erase(it);
		return temp; 
	} 
	
	directory*cd(string name){
    
    
		if(name==".."){
    
    return this->parent;}
		return getchild(name);//不存在会返回来NULL 
	}
	
	void maintain(int d){
    
    
		this->updated=1; 
		subtreeSize+=d;
		if(parent!=NULL){
    
    
			parent->maintain(d);
		}
	}
	
	void sz(){
    
    //输出当前目录的大小
		printf("%d\n",this->subtreeSize);
	}
		
	void ls(){
    
    //输出多行表示当前目录的 "直接子目录" 名
		int sz=children.size();//孩子数
		if(sz==0){
    
    printf("EMPTY\n");}
		else if(sz<=10){
    
    
			for(auto &i:children)
				printf("%s\n",i.first.c_str());
		}else{
    
    
			auto it=children.begin();
			for(int i=0;i<5;i++,it++)
				printf("%s\n",it->first.c_str());
			printf("...\n");
			it=children.end();
			for(int i=0;i<5;i++)it--;
			for(int i=0;i<5;i++){
    
    //一开始忘了it++ 
				printf("%s\n",it->first.c_str()); 
				it++;
			}
		}
	}
	
	void tree(){
    
    
		if(subtreeSize==1)printf("EMPTY\n");
		else if(subtreeSize<=10){
    
    //误以为+1 
			if(this->updated){
    
    //更新过 
				ten->clear();
				treeAll(ten);
				this->updated=0; 
			}
			for(int i=0;i<subtreeSize;i++){
    
    
				printf("%s\n",ten->at(i).c_str());
			}
		}else{
    
    
			if(this->updated){
    
    //更新过 
				ten->clear();
				treeFirst(5,ten);
				treeLast(5,ten);
				this->updated=0; 
			}
			for(int i=0;i<5;i++){
    
    
				printf("%s\n",ten->at(i).c_str());
			}
			printf("...\n");
			for(int i=9;i>=5;i--){
    
    
				printf("%s\n",ten->at(i).c_str());
			}
		}
	}
private:
	void treeAll(vector<string>*v){
    
    
		v->push_back(name);
		for(auto &i:children){
    
    
			i.second->treeAll(v);
		}
	}
	void treeFirst(int num,vector<string>*v){
    
    
		v->push_back(name);
		if(--num==0){
    
    return;}
		int n=children.size();
		auto it=children.begin();
		while(n--){
    
    
			int sts=it->second->subtreeSize;//孩子的大小 
			if(sts>=num){
    
    
				it->second->treeFirst(num,v);
				return;
			} else{
    
    
				it->second->treeFirst(sts,v);错写为num 
				num-=sts; 
			}
			it++;
		}
	}
	void treeLast(int num,vector<string>*v){
    
    
		int n=children.size();
		auto it=children.end();
		while(n--){
    
    
			it--;
			int sts=it->second->subtreeSize;//孩子的大小 
			if(sts>=num){
    
    
				it->second->treeLast(num,v);
				return;
			} else{
    
    
				it->second->treeLast(sts,v);
				num-=sts; 
			}
		}
		v->push_back(name);
	}	
};

struct Command{
    
    
	const string CMD_NAMES[7]={
    
    "MKDIR","RM","CD","SZ","LS","TREE","UNDO"}; 
	int type;//命令的类型 0->mkdir 1->rm 2->cd
			 //3->sz 4->ls 5->tree 6->undo  
	string arg;//命令参数
	directory*tmpdir;//刚刚操作涉及的目录节点 
	Command(string s){
    
    //构造函数 
		tmpdir=NULL;
		for(int i=0;i<7;i++)if(CMD_NAMES[i]==s){
    
    
			type=i;
			if(type<3){
    
    scanf("%s",tmps);arg=tmps;}
			return;
		} 
	} 
};
vector<Command*>cmdlist;//已经成功的命令

void solve(){
    
    //对每一组测试数据 
	int n; scanf("%d",&n);//每组数据有n行
	directory*now=new directory("root",NULL);
	vector<Command*>cmdlist;//已经成功的命令
	while(n--){
    
     
	 	scanf("%s",tmps);
	 	Command*cmd=new Command(tmps);
	 	switch(cmd->type)
		{
    
    
	 	case 0:{
    
    //mkdir
	 		cmd->tmpdir=now->mkdir(cmd->arg);
	 		if(cmd->tmpdir==NULL){
    
    printf("ERR\n");}
			else{
    
    
				printf("OK\n");
				cmdlist.push_back(cmd); 
			}
			break;
		}
	 	case 1:{
    
    //rm
	 		cmd->tmpdir=now->rm(cmd->arg);
	 		if(cmd->tmpdir==NULL){
    
    printf("ERR\n");}
			else{
    
    
				printf("OK\n");
				cmdlist.push_back(cmd); 
			}
			break;
		}
	 	case 2:{
    
    
		 	//cd
	 		directory*ch=now->cd(cmd->arg);//
	 		if(ch==NULL){
    
    printf("ERR\n");}
			else{
    
    
				printf("OK\n");
				cmd->tmpdir=now;//将当前目录存起来,undo直接回这里 
				now=ch;//进入新目录 
				cmdlist.push_back(cmd); 
			}
			break;
		}
	 	case 3://sz
			now->sz();break;
	 	case 4://ls
			now->ls();break;
	 	case 5://tree
	 		now->tree();break;
	 	case 6:{
    
    //undo
			bool success=false;
			if(!cmdlist.empty()){
    
    //一开始这里是while 
				cmd=cmdlist.back();cmdlist.pop_back();
				switch(cmd->type){
    
    
					case 0:success=now->rm(cmd->tmpdir->name)!=NULL;break;
					case 1:success=now->addchild(cmd->tmpdir)!=NULL;break;
					case 2:now=cmd->tmpdir;success=1;break;
				} 
			} 
			printf(success?"OK\n":"ERR\n");
			break;
		}
		}
	} 
}

int main(int argc, char** argv) {
    
    
	int t;scanf("%d",&t);
	while(t--){
    
    solve();}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/alicemh/article/details/105625388
今日推荐