题目:
咕咕东的雪梨电脑的操作系统在上个月受到宇宙射线的影响,时不时发生故障,他受不了了,想要写一个高效易用零bug的操作系统 —— 这工程量太大了,所以他定了一个小目标,从实现一个目录管理器开始。前些日子,东东的电脑终于因为过度收到宇宙射线的影响而宕机,无法写代码。他的好友TT正忙着在B站看猫片,另一位好友瑞神正忙着打守望先锋。现在只有你能帮助东东!
初始时,咕咕东的硬盘是空的,命令行的当前目录为根目录 root。
目录管理器可以理解为要维护一棵有根树结构,每个目录的儿子必须保持字典序。
现在咕咕东可以在命令行下执行以下表格中描述的命令:
输入:
输入文件包含多组测试数据,第一行输入一个整数表示测试数据的组数 T (T <= 20);
每组测试数据的第一行输入一个整数表示该组测试数据的命令总数 Q (Q <= 1e5);
每组测试数据的 2 ~ Q+1 行为具体的操作 (MKDIR、RM 操作总数不超过 5000);
输出:
每组测试数据的输出结果间需要输出一行空行。注意大小写敏感。
样例输入:
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
OK
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
这一题应该是这门课目前为止最复杂的题了。
这一题我是看了3、4班的学长的代码,学长用了面向对象的思想进行编程,之前一直没有有意识地用面向对象的思想针对这门课的题目进行编程,但是看了学长的代码后才认识到在这种复杂的题目前,面向对象的思想可以让我们很清晰地去编写代码。
先是Command结构体,构造函数的参数string s为指令名字。
struct Command {
const string COMMAND_NAMES[7] = { "MKDIR","RM","CD","SZ","LS","TREE","UNDO" };
int type;//指令的类型
string arg;//指令的参数
Directory* tmpDir;//记录刚刚操作的目录,用于undo
Command(string s)
{
for (int i = 0; i < 7; i++)
{
type = i;
if (COMMAND_NAMES[i] == s)
{
if (i < 3)//前三种操作有参数
{
cin >> arg;
}
return;
}
}
}
};
然后是Directory结构体:
这是公有数据成员:
string name;//本目录的名字
map<string, Directory*> children;//利用map可以通过目录的名字来查找,同时复杂度为O(logn)
Directory* parent; //存储上一级目录
int subtreeSize; //子树的大小
vector<string> tenNode;//存储最多10个子节点
bool updated;//判断tenNode是否更新过
然后是相应的函数:
构造函数:
Directory(string na, Directory* pa)
{
name = na;
parent = pa;
subtreeSize = 1;
}
函数sz,输出以当前节点为根节点的子树的节点个数,因为Directory结构体中有subtreeSize,所以直接输出subtreeSize就行了。
void sz()
{
cout << subtreeSize << endl;
}
ls函数,根据题意,需要分成3种情况,分别是子节点个数为0,小于等于10 和大于10。
void ls()
{
int sz = children.size();//返回子节点的个数
if (sz == 0)
{
cout << "EMPTY\n";
}
else if (sz <= 10)//子目录个数小于等于10,全部输出
{
map<string, Directory*>::iterator it;
for (it = children.begin(); it != children.end(); it++)
{
cout << it->first << '\n';
}
}
else
{
map<string, Directory*>::iterator it = children.begin();
for (int i = 0; i < 5; i++, it++)//输出前五个
{
cout << it->first << '\n';
}
cout << "...\n";
it = children.end();
for (int i = 0; i < 5; i++)
{
it--;
}
for (int i = 0; i < 5; i++, it++)//输出后五个
{
cout << it->first << "\n";
}
}
}
maintain函数,递归,自下而上地更新节点的subtreeSize:
void maintain(int sz)
{
updated = true;//将updated置为true,说明树被更新过了
subtreeSize += sz;
if (parent != NULL)
{
parent->maintain(sz);//递归维护sutreeSize
}
}
addChild、getChild、mkdir、rm、cd函数:
bool addChild(Directory* ch)
{
if (children.find(name) != children.end())//已经存在了
{
return false;
}
children[ch->name] = ch;
maintain(+ch->subtreeSize);
return true;
}
Directory* getChild(string name)
{
map<string, Directory*>::iterator it = children.find(name);
if (it == children.end())//没找到目标
{
return NULL;
}
return it->second;
}
Directory* mkdir(string str)
{
if (children.find(str) != children.end())//已经存在了
{
return NULL;
}
Directory* ch = new Directory(str, this);//new新节点
children[str] = ch;
maintain(+1);//维护subtreeSize
return ch;
}
Directory* rm(string str)
{
map<string, Directory*>::iterator it = children.find(str);
if (it == children.end())//不存在要删除的节点
{
return NULL;
}
maintain(-1 * it->second->subtreeSize);//一次删掉了str下所有的目录
Directory* temp = it->second;
children.erase(it);
return temp;
}
Directory* cd(string str)
{
if (".." == str)//".."表示当前目录的上一级
{
return this->parent;
}
return getChild(str);
}
tree函数,因为复杂度的要求,如果每次tree指令都要遍历一遍树的话复杂度太高,可以用懒更新的思想进行优化,updated用于判断树是否更新过,根据这个来决定要不要重新遍历树:
void tree()
{
if (subtreeSize == 1)//没有子目录
{
cout << "EMPTY\n";
}
else if (subtreeSize <= 10)
{
if (updated)//先判断子树有没有被更新过
{
tenNode.clear();
treeAll(tenNode);
updated = false;
}
for (int i = 0; i < subtreeSize; i++)
{
cout << tenNode[i] << '\n';
}
}
else
{
if (updated)//先判断子树有没有被更新过
{
tenNode.clear();
treeFirst(5, tenNode);
treeLast(5, tenNode);
updated = false;
}
for (int i = 0; i < 5; i++)
{
cout << tenNode[i] << '\n';
}
cout << "...\n";
for (int i = 9; i >= 5; i--)
{
cout << tenNode[i] << '\n';
}
}
}
treeAll函数类似于中序遍历:
void treeAll(vector<string>& bar)
{//中序遍历
bar.push_back(name);
for (map<string, Directory*>::iterator it = children.begin(); it != children.end(); it++)
{
it->second->treeAll(bar);
}
}
treeFirst函数:
void treeFirst(int num, vector<string>& bar)
{
bar.push_back(name);
if (--num == 0)
return;
int n = children.size();
map<string, Directory*>::iterator it = children.begin();
for (int i = 0; i < n; i++)
{
int child_size = it->second->subtreeSize;
if (child_size >= num)//如果以当前节点的子节点的子节点数已经大于等于num了,则不需要管当下节点的其他子节点了,继续往下即可
{
it->second->treeFirst(num, bar);
return;//这个return使得中间的节点被忽略了,没有压入bar中
}
else
{
it->second->treeFirst(child_size, bar);
num -= child_size;
}
it++;
}
}
treeLast函数的过程类似于下图:
void treeLast(int num, vector<string>& bar)//类似于后序遍历
{
int n = children.size();
map<string, Directory*>::iterator it = children.end();
for (int i = 0; i < n; i++)
{
it--;
int child_size = it->second->subtreeSize;
if (child_size >= num)//如果以当前节点的子节点的子节点数数已经大于等于num了,则不需要管当下节点的其他子节点了,继续往下即可
{
it->second->treeLast(num, bar);
return;//这个return使得中间的节点被忽略了,没有压入bar中
}
else
{
it->second->treeLast(child_size, bar);
num -= child_size;
}
}
bar.push_back(name);
}
之后是solve函数,通过这个函数判断输入的是什么指令,然后用switch case进行相应地操作,调用相应的函数。
void solve()
{
int n;
string temp;
vector<Command*> c;//存储成功执行的命令,便于执行undo操作
cin>>n;
Directory* now = new Directory("root", NULL);
for (int i = 0; i < n; i++)
{
cin >> temp;
Command* cmd = new Command(temp);//用指针是为了避免浅拷贝,在本函数后面有相应的情况
switch (cmd->type)
{
case 0:
{
cmd->tmpDir = now->mkdir(cmd->arg);
if (cmd->tmpDir == NULL)
{
cout << "ERR\n";
}
else
{
cout << "OK\n";
c.push_back(cmd);//每次都将成功的操作放入vector
}
break;
}
case 1:
{
cmd->tmpDir = now->rm(cmd->arg);
if (cmd->tmpDir == NULL)
{
cout << "ERR\n";
}
else
{
cout << "OK\n";
c.push_back(cmd);
}
break;
}
case 2:
{
Directory* t = now->cd(cmd->arg);
if (t == NULL)
{
cout << "ERR\n";
}
else
{
cout << "OK\n";
cmd->tmpDir = now;
now = t;//当下的目录切换
c.push_back(cmd);
}
break;
}
case 3:
{
now->sz();
break;
}
case 4:
{
now->ls();
break;
}
case 5:
{
now->tree();
break;
}
case 6:
{
bool success = false;
while (!success && !c.empty())
{
cmd = c.back();//因为cmd和c.back()返回值都是指针类型,所以不需要为Command重载“=”,否则需要重载“=”,不然浅拷贝的话,指针Directory*会出问题
c.pop_back();
switch (cmd->type)
{
case 0:
success = (now->rm(cmd->arg) != NULL);
break;
case 1:
success = (now->addChild(cmd->tmpDir));
break;
case 2:
now = cmd->tmpDir;
success = true;
break;
}
}
if (success)
{
cout << "OK\n";
}
else
{
cout << "ERR\n";
}
break;
}
}
}
}
以下是完整代码:
#include<iostream>
#include<vector>
#include<cstring>
#include<string>
#include<map>
using namespace std;
struct Directory {
public:
string name;
map<string, Directory*> children;//利用map可以通过目录的名字来查找
Directory* parent; //存储上一级目录
int subtreeSize; //子树的大小
vector<string> tenNode;//存储最多10个子节点
bool updated;//判断tenNode是否更新过
Directory(string na, Directory* pa)
{
name = na;
parent = pa;
subtreeSize = 1;
}
bool addChild(Directory* ch)
{
if (children.find(name) != children.end())//已经存在了
{
return false;
}
children[ch->name] = ch;
maintain(+ch->subtreeSize);
return true;
}
Directory* getChild(string name)
{
map<string, Directory*>::iterator it = children.find(name);
if (it == children.end())//没找到目标
{
return NULL;
}
return it->second;
}
Directory* mkdir(string str)
{
if (children.find(str) != children.end())//已经存在了
{
return NULL;
}
Directory* ch = new Directory(str, this);//new新节点
children[str] = ch;
maintain(+1);//维护subtreeSize
return ch;
}
Directory* rm(string str)
{
map<string, Directory*>::iterator it = children.find(str);
if (it == children.end())//不存在要删除的节点
{
return NULL;
}
maintain(-1 * it->second->subtreeSize);//一次删掉了str下所有的目录
Directory* temp = it->second;
children.erase(it);
return temp;
}
Directory* cd(string str)
{
if (".." == str)//".."表示当前目录的上一级
{
return this->parent;
}
return getChild(str);
}
void sz()
{
cout << subtreeSize << endl;
}
void ls()
{
int sz = children.size();//返回子节点的个数
if (sz == 0)
{
cout << "EMPTY\n";
}
else if (sz <= 10)//子目录个数小于等于10,全部输出
{
map<string, Directory*>::iterator it;
for (it = children.begin(); it != children.end(); it++)
{
cout << it->first << '\n';
}
}
else
{
map<string, Directory*>::iterator it = children.begin();
for (int i = 0; i < 5; i++, it++)//输出前五个
{
cout << it->first << '\n';
}
cout << "...\n";
it = children.end();
for (int i = 0; i < 5; i++)
{
it--;
}
for (int i = 0; i < 5; i++, it++)//输出后五个
{
cout << it->first << "\n";
}
}
}
void tree()
{
if (subtreeSize == 1)//没有子目录
{
cout << "EMPTY\n";
}
else if (subtreeSize <= 10)
{
if (updated)//先判断子树有没有被更新过
{
tenNode.clear();
treeAll(tenNode);
updated = false;
}
for (int i = 0; i < subtreeSize; i++)
{
cout << tenNode[i] << '\n';
}
}
else
{
if (updated)//先判断子树有没有被更新过
{
tenNode.clear();
treeFirst(5, tenNode);
treeLast(5, tenNode);
updated = false;
}
for (int i = 0; i < 5; i++)
{
cout << tenNode[i] << '\n';
}
cout << "...\n";
for (int i = 9; i >= 5; i--)
{
cout << tenNode[i] << '\n';
}
}
}
void maintain(int sz)
{
updated = true;//将updated置为true,说明树被更新过了
subtreeSize += sz;
if (parent != NULL)
{
parent->maintain(sz);//递归维护sutreeSize
}
}
private:
void treeAll(vector<string>& bar)
{//中序遍历
bar.push_back(name);
for (map<string, Directory*>::iterator it = children.begin(); it != children.end(); it++)
{
it->second->treeAll(bar);
}
}
void treeFirst(int num, vector<string>& bar)
{//中序遍历
bar.push_back(name);
if (--num == 0)
return;
int n = children.size();
map<string, Directory*>::iterator it = children.begin();
for (int i = 0; i < n; i++)
{
int child_size = it->second->subtreeSize;
if (child_size >= num)//如果以当前节点的子节点的子节点数已经大于等于num了,则不需要管当下节点的其他子节点了,继续往下即可
{
it->second->treeFirst(num, bar);
return;
}
else
{
it->second->treeFirst(child_size, bar);
num -= child_size;
}
it++;
}
}
void treeLast(int num, vector<string>& bar)//类似于后序遍历
{
int n = children.size();
map<string, Directory*>::iterator it = children.end();
for (int i = 0; i < n; i++)
{
it--;
int child_size = it->second->subtreeSize;
if (child_size >= num)//如果以当前节点的子节点的子节点数数已经大于等于num了,则不需要管当下节点的其他子节点了,继续往下即可
{
it->second->treeLast(num, bar);
return;
}
else
{
it->second->treeLast(child_size, bar);
num -= child_size;
}
}
bar.push_back(name);
}
};
struct Command {
const string COMMAND_NAMES[7] = { "MKDIR","RM","CD","SZ","LS","TREE","UNDO" };
int type;//指令的类型
string arg;//指令的参数
Directory* tmpDir;//记录刚刚操作的目录,用于undo
Command(string s)
{
for (int i = 0; i < 7; i++)
{
type = i;
if (COMMAND_NAMES[i] == s)
{
if (i < 3)//前三种操作有参数
{
cin >> arg;
}
return;
}
}
}
};
void solve()
{
int n;
string temp;
vector<Command*> c;//存储成功执行的命令,便于执行undo操作
cin>>n;
Directory* now = new Directory("root", NULL);
for (int i = 0; i < n; i++)
{
cin >> temp;
Command* cmd = new Command(temp);
switch (cmd->type)
{
case 0:
{
cmd->tmpDir = now->mkdir(cmd->arg);
if (cmd->tmpDir == NULL)
{
cout << "ERR\n";
}
else
{
cout << "OK\n";
c.push_back(cmd);//每次都将成功的操作放入vector
}
break;
}
case 1:
{
cmd->tmpDir = now->rm(cmd->arg);
if (cmd->tmpDir == NULL)
{
cout << "ERR\n";
}
else
{
cout << "OK\n";
c.push_back(cmd);
}
break;
}
case 2:
{
Directory* t = now->cd(cmd->arg);
if (t == NULL)
{
cout << "ERR\n";
}
else
{
cout << "OK\n";
cmd->tmpDir = now;
now = t;//当下的目录切换
c.push_back(cmd);
}
break;
}
case 3:
{
now->sz();
break;
}
case 4:
{
now->ls();
break;
}
case 5:
{
now->tree();
break;
}
case 6:
{
bool success = false;
while (!success && !c.empty())
{
cmd = c.back();//因为cmd和c.back()返回值都是指针类型,所以不需要为Command重载“=”,否则需要重载“=”,不然浅拷贝的话,指针Directory*会出问题
c.pop_back();
switch (cmd->type)
{
case 0:
success = (now->rm(cmd->arg) != NULL);
break;
case 1:
success = (now->addChild(cmd->tmpDir));
break;
case 2:
now = cmd->tmpDir;
success = true;
break;
}
}
if (success)
{
cout << "OK\n";
}
else
{
cout << "ERR\n";
}
break;
}
}
}
}
int main()
{
int T;
cin >> T;
for (int i = 0; i < T; i++)
{
solve();
}
}