ZOJ 1635 - Directory Listing 解题报告

简介: 题目:1635 Directory Listing(列出目录)        http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=635         看描述好像是chenyue姐姐出的题目。

       题目:1635 Directory Listing(列出目录)

       http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=635

        看描述好像是chenyue姐姐出的题目。这道题的大意是,给出一个UNIX文件系统的树,以及树节点的自身size,按要求列出它们,保持适当的缩进,并统计每个节点的总的size。输入的第一行代表根结点,每一个节点由name size或者*name size的形式组成(如果包含*表示这是一个文件夹,它包含的子节点将在下一行中列出,并用圆括号包含,size是一个表示节点自身属性的正整数)。在输出节点时,需要输出该节点自身size和所有子节点size的和。

       深度为d的节点前面应该有8d的前导(由空格或竖线组成);

 

示范输入:

*/usr 1
(*mark 1 *alex 1)
(hw.c 3 *course 1) (hw.c 5)
(aa.txt 12)


 

示范输出:

|_*/usr[24]
        |_*mark[17]
        |       |_hw.c[3]
        |       |_*course[13]
        |               |_aa.txt[12]
        |_*alex[6]
                |_hw.c[5]

        ---------------------------------------------------------------------------------------
       题目属于按固定格式输出的题目,但本质上是属于对“树”这种数据结构的考察。不难看出,在读取输入时,输入方式实际上是一种类似树的“按层次遍历”,即按照节点深度从小到大的顺序给出每一层次的节点,第k行即代表了该深度(depth=k)的所有节点。同时,如果节点不包含*,表示是叶子节点,如果包含*,则属于“文件夹”,因此一定会有下一行。如果某一行不包含任何*,则一棵树输入完毕。

       而对于输出,容易看出,输出实际上是对树的前序遍历。

       在我的此前的一篇博客中,已经详细讲解过如何通过栈来前序遍历树,以及如何通过队列来层次遍历树,因此这里不再详细介绍了。输出的时候完全是前序遍历,无须多做解释。唯一有点特别的是读取输入的过程就是组装这颗树的过程,组装过程中我们先把根结点(一定包含*)入列,然后依次读取下一行,下一行中假设包含n组子节点(即含有n组圆括号),则针对每一组子节点,从队列中出列一个节点作为这些子节点的父节点即可。直到队列为空,一棵树的读取和组装完毕。

       这里我们还要注意的第一个细节问题是,在组装过程中,我们还要把该节点的最终size统计出来。因此我们给每个树节点增加了一个父节点指针,这样每挂接一个子节点,从该节点向根结点追溯,对所有父辈节点累加该size。此外我们还需要一个属性记录该节点的所在深度,这个属性在后面的输出时被利用。因此,我们把节点定义如下:

 

typedef struct tnode
{
      char text[11];/*节点名称*/
      int depth; /*该节点所在层次*/
      int size; /*该节点的当前值,最终值=自身值+所有子节点值*/
      struct tnode *child[10]; /*根结点*/
      struct tnode *parent; /*父节点,用于向根结点累加size*/
} TNODE;

 

       下面我们即可给出读取并组装树的代码。值得注意的是,我们每次读取一行,而该行中包含的子节点组的个数n是未知的,因此我们需要对读取进来的这一行通过ParseLine函数进行解析,即根据'('和')'字符,把圆括号内部的文本提取出来,再传递给AddChildNodes函数进行处理。

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif Code_InputTree
TNODE *queue[100];/*队列,用于输入*/
TNODE 
*stack[100];/*栈,用于输出*/
int head, tail;        /*队列的全局指针*/
int top;                    /*栈的全局指针*/

TNODE
* InputTree();
void ParseLine(char*int);
void AddChildNodes(char*int);
void OutputTree(TNODE *head);

/*读入并组装为一棵树,返回根结点指针*/
TNODE
* InputTree()
{
    
int size, depth=0;/*队列的指针*/
    
char buf[11], line[1024];
    TNODE 
*node=NULL, *root=NULL;
    
/*是否读到了文件尾部,如果读到空字符串也返回null*/
    
if(scanf("%s %d", buf, &size)==EOF || size<0)
        
return NULL;
    
    head
=tail=0;
    
/*申请首个节点空间,赋值,入列*/
    root
=(TNODE*)malloc(sizeof(TNODE));
    memset(root, 
0sizeof(TNODE));
    root
->size=size;
    strcpy(root
->text, buf);    
    queue[tail
++]=root; /*首个节点入队列*/
    
    
/*循环读入后续的每一行*/
    
while(head!=tail)
    {
        gets(line);
        ParseLine(line, depth
++); /*解析这一行!实际就是装配树的过程!*/
        
/*如果这一行中没有星号,说明树已经输入完毕!*/
    }
    
return root;    
}

/*解析当前输入的一行,depth为节点所在深度*/
void ParseLine(char *line, int depth)
{
    
char *begin, *s;
    
int flag=0;
    begin
=s=line;
    
while(*s)
    {
        
switch(*s)
        {
            
case '(':
                begin
=s+1;
                
break;
            
case ')':
                
*s='\0';
                AddChildNodes(begin, depth);
                
break;
        }
        s
++;
    }    
}

/*添加子节点, depth为节点所在深度*/
void AddChildNodes(char *str, int depth)
{
    TNODE 
*parent, *node, *temp;
    
char *token;
    
int i=0;/*子节点索引*/

    token
=strtok(str, " ");
    
/*出列一个节点!*/
    parent
=queue[head++];
    
while(token!=NULL)
    {
        node
=(TNODE*)malloc(sizeof(TNODE));
        memset(node, 
0 ,sizeof(TNODE));
        node
->depth=depth;
        strcpy(node
->text, token);
        
/*看该节点是否应该入列,如果是文件夹,则入列!*/
        
if(token[0]=='*')
            queue[tail
++]=node;
        
        
/*读入size*/
        token
=strtok(NULL, " ");
        node
->size=atoi(token);
        
        
/*添加该子节点!*/
        parent
->child[i++]=node;
        node
->parent=parent;
        
        
/*累加父节点的size!*/
        temp
=parent;
        
while(temp!=NULL)
        {
            temp
->size+=node->size;
            temp
=temp->parent;
        }
        token
=strtok(NULL, " ");
    }
}

 

       注意在上面的代码中,我们在AddChildNodes函数的最后面对节点的size进行向根结点方向的追踪累加,这样树组装完毕后,每个节点的size就是最终要输出的值。

       当树读取进来并且组装以后,我们只需要对这个树进行前序遍历,依次打印节点即可,这部分的内容比较简单。但显然在输出中存在很关键的打印技巧,即如何准确打印出树的形状,每一个节点前面的前导字符串是关键!每一行由下面的格式组成:

       [前缀字符串] |_ [节点名称] [size]

       这里唯一有难度和技巧性的是前缀字符串的确定,每一深度的缩进由8个空格组成,但有时第一个空格实际上是竖线。那么竖线是否出现的规律是什么呢?仔细观察我们可以发现,当该节点的父节点是祖父节点的最后一个子节点(即老幺)时,父节点下方将不包含竖线。否则这8个空格中的第一个被替换为'|'。这样我们就不难写出下面的代码,提供一个缓冲区,然后我们把它设置为正确的前缀字符串:

 

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif Code_设置前缀字符串
/*很关键的一个函数,设置某个节点的前导部分*/
void SetPrefix(TNODE *node, char *buffer)
{
    
int i, depth=node->depth;
    TNODE 
*temp, *parent=NULL;
    
if(node->depth>0)
        memset(buffer, 
' ', node->depth * 8);
    buffer[node
->depth * 8]='\0'/*结尾*/

    
/*从下向上去判断!注意depth=0时无需要判断,因为根结点是“独子”,注定是“老幺”*/
    
    temp
=node->parent;
    
while(temp!=NULL && (temp->depth > 0))
    {
        parent
=temp->parent;
        
/*从父节点的子节点集合中最后向前找,如果不是最后一个节点,则把空格改为竖线*/
        
for(i=9;i>=0;i--)
        {
            
/*如果temp不是父节点的最后一个子节点(老幺)*/
            
if(parent->child[i]!=NULL && parent->child[i]!=temp)
            {
                buffer[temp
->depth*8]='|';
                
break;
            }
            
if(parent->child[i]==temp) /*是最后一个节点*/
                
break;
        }
        temp
=parent;
    }
}

 

         最后我们就可以用常规的栈前序遍历输出结果:

 

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif Code_用栈辅助来前序遍历输出
/*借助栈的辅助,前序输出,并输出后释放相应节点!*/
void OutputTree(TNODE *head)
{
    TNODE 
*node;
    
char prefix[80];/*该节点的前导字符串*/
    
int i;
    
    
if(head==NULL)
        
return;
    top
=0/*栈顶指针*/
    
    
/*根结点入栈*/
    stack[top
++]=head;
    
    
/*当栈不为空时*/
    
while(top>0)
    {
        node
=stack[--top]; /*pop a node*/
        
/*在这里我们设置节点的前导!非常关键的一个地方!!!*/
        SetPrefix(node, prefix);
                
        
/*打印该节点*/
        printf(
"%s|_%s[%d]\n", prefix, node->text, node->size);
        
        
/*node的子节点从右到左入栈!*/
        
for(i=9;i>=0;i--)
        {
            
if(node->child[i]!=NULL)
                stack[top
++]=node->child[i];
        }        
    }
}

 

         同时我们在输出完一棵树以后,在读取下一颗树之前,应当把当前的树说申请的内存还给系统。由于刚刚使用栈输出过这棵树,因此这棵树的所有节点实际上都位于辅助栈内。因此我们只需要把栈里所有节点释放即可:

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif Code_释放树占用的内存
/*此时应该是刚刚输出完毕树,只要清空堆栈里的所有节点即可*/
void DeleteTree()
{
    
int i=0;
    
for(i=0;i<100;i++)
    {
        
if(stack[i]==NULL)
            
break;
        free(stack[i]);
        stack[i]
=NULL;
    }    
}

/*入口点*/
int main()
{
    TNODE 
*tree;
    
while((tree=InputTree())!=NULL)
    {
        OutputTree(tree);
        DeleteTree();
/*释放栈中的所有节点!*/
    }
    
return 0;
}

         最后,我们再关注一下需要注意的事项:

         (1)我们在输出所有节点以后,才统一的一次性释放所有节点。而不是每输出一个节点就当即释放一个节点。这样做的原因是我们需要用节点关系去推导前缀字符串,因此我们没有立即就释放已经遍历过的节点。 

       (2)我们使用malloc申请了一个节点以后,请注意这样的内存是“原始”的,也就是未经过任何初始化的,因此如果里面包含指针(例如节点的父节点指针,所有子节点指针),则这些指针全部属于“野指针”状态。如果不对这些指针进行初始化,将会导致灾难性后果。所以每次申请到一个节点后我们必须要做的第一步是初始化它:即memset( node, 0, sizeof(TNODE)); 这一条语句将保证节点中所有指针被设置为NULL,切不可忘记!!!

        (3)实际上由于输入和输出是完全顺序的操作,因此这里的辅助栈和辅助队列可以合并为一个,它是queue还是stack完全取决于我们如何使用它。但为了清晰起见,我们的代码还是保留为独立的queue和stack空间。

        (4)此题看似容易,但实际上包含了相当的技巧性,并不是很容易在短时间内写好,从它较低的AC率(24%)来看说明它也不是一道简单的送分题。我仔细调试和检查了代码,直到全部正确才提交,然后一次性AC了,运行时间和内存占用都和解排行榜上的数据一致。

 

 

目录
相关文章
|
弹性计算 监控 区块链
全网最全的幻兽帕鲁服务器搭建教程—阿里云【保姆级/高性价比】
在近年来,随着区块链技术和元宇宙概念的兴起,游戏行业也开始进行全新的探索和变革。幻兽帕鲁作为一个区块链游戏,成为了玩家们热议的话题。在这个游戏世界中,玩家们可以捕捉、培养幻兽,与其他玩家进行战斗和交易。为了让更多玩家能够体验到幻兽帕鲁的魅力,我们特地整理了一份详细的服务器搭建教程,让你在家也能轻松搭建自己的幻兽帕鲁服务器。
302937 70
|
数据可视化 Python
Matplotlab可视化学习笔记(二):如何绘制柱状图
使用Matplotlib库在Python中绘制柱状图的教程,包括基本的柱状图绘制和多条柱状图的绘制方法。
137 1
Matplotlab可视化学习笔记(二):如何绘制柱状图
|
存储 算法 搜索推荐
作为程序员必须掌握的经典算法
作为程序员必须掌握的经典算法
|
存储 资源调度 算法
基于图像形态学处理和边缘提取算法的路面裂痕检测matlab仿真
基于图像形态学处理和边缘提取算法的路面裂痕检测matlab仿真
|
机器学习/深度学习 自然语言处理 算法
ACL 2019 - AMR Parsing as Sequence-to-Graph Transduction
我们提出了一个基于注意力的模型,将AMR解析视为序列到图的转导。与大多数依赖于预训练的对齐器、外部语义资源或数据扩充的AMR解析器不同
293 0
ACL 2019 - AMR Parsing as Sequence-to-Graph Transduction
链表带环问题
链表带环问题
202 0
|
Serverless Cloud Native 容器
Knative 系列文章目录
初识 Knative: 跨平台的 Serverless 编排框架 Knative 入门 Knative 核心概念介绍:Build、Serving 和 Eventing 三大核心组件 Knative 初体验:Serving Hello World Knative 初体验:Eventing Hel.
9744 0
|
3天前
|
存储 弹性计算 人工智能
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
2025年9月24日,阿里云弹性计算团队多位产品、技术专家及服务器团队技术专家共同在【2025云栖大会】现场带来了《通用计算产品发布与行业实践》的专场论坛,本论坛聚焦弹性计算多款通用算力产品发布。同时,ECS云服务器安全能力、资源售卖模式、计算AI助手等用户体验关键环节也宣布升级,让用云更简单、更智能。海尔三翼鸟云服务负责人刘建锋先生作为特邀嘉宾,莅临现场分享了关于阿里云ECS g9i推动AIoT平台的场景落地实践。
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
|
1天前
|
云安全 人工智能 自然语言处理
阿里云x硅基流动:AI安全护栏助力构建可信模型生态
阿里云AI安全护栏:大模型的“智能过滤系统”。