PTA习题解析——目录树

目录树#


看到这个问题,我们是一头雾水啊,这讲了个啥?别急,让我们用测试样例模拟一遍。

样例模拟#

首先我们先考虑存储数据的方式,根据观察和我们对文件夹的理解,对于一个文件夹而言,与其他文件或文件夹只会有 2 种关系——和我在同一目录、在我的目录,也就是只有同级和下级两种关系。因此我们就很自然地想到孩子兄弟表示法,因为在孩子兄弟对于一个结点也只会有 2 种关系——孩子和兄弟,那我们就用孩子表示在下级目录,兄弟表示在同级目录。

首先我们拥有一个根目录 root,读取第一行数据,表示根目录有个名为 b 的文件,是 root 的孩子,因此根据孩子兄弟表示法,b 应该是 root 的左分支结点。

读取第二行数据,在根目录中有个 c 目录。因此 c 目录是 root 的孩子,并且与 b 文件同级,也就是互为兄弟关系,进行结点的加入。

读取第三行数据,在根目录中有个 ab 目录,并在这个目录下有个 cd 文件。因此 ab 目录是 root 的孩子,并且与 c 目录同级,也就是互为兄弟关系,同时 ab 目录有个左分支 cd,进行结点的加入。

读取第四行数据,在根目录中有个 a 目录,并在这个目录下有个 bc 文件。因此 a 目录是 root 的孩子,并且与 ab 目录同级,也就是互为兄弟关系,同时 a 目录有个左分支 bc,进行结点的加入。

读取第五行数据,在 ab 目录下有个 d 文件。由于 ab 已经存在,因此 d 与 cd 互为兄弟关系,修改 ab 的孩子为 d,d 的兄弟为 cd,进行结点的加入。

重复上述操作完成建树。


把这棵树整理成二叉树的形式。

我们按照前序遍历的顺序读一下这课树,发现在忽略缩进的情况下,读取的顺序和样例的输出数据是一模一样啊,也就是说,只要把这棵树建出来,这个情景我们就解决了。在应用的时候,我们应当积极地考虑使用孩子兄弟表示法建树,因为这种方法建立的是二叉树,我们就可以用二叉树的基操来操作这棵树。

结点结构体定义#

Copy Highlighter-hljs
typedef struct CSNode { string data; //数据域 struct CSNode* firstchild; //指向对应长子结点的指针域 struct CSNode* rightsib; //指向对应右兄弟结点的指针域 int flag_file; //判断是文件还是目录的 flag }CSNode, * CSTree;
  • 此处为什么用 int 类型来当 flag 而不是 bool 类型呢?这是因为改为 int 类型,相当于直接赋予优先级,判断插入位置时直接在同优先级的结点进行查找即可。

建树算法#

字符串切片算法#

切片算法可以用字符数组实现,也可以用 string 类实现,此处我用字符数组描述。由于我们读入的数据是字符串,因此我们需要先把各个目录分离开来。需要注意的是,虽然文件是特殊数据,但是文件只会出现在字符串串尾,因此只需要一个分支结构单独处理即可。

伪代码#


需要强调的是,string 类用来判断字典序和复制操作都可以直接用运算符实现,更为方便。

代码实现#

调试结果#

伪代码#

建树算法只需要再字符串切片算法进行改动即可,把输出语句改为调用结点插入函数即可实现。

代码实现#

Copy Highlighter-hljs
void createTree(CSTree pre, string str) { int idx = 0; getline(cin, str); for (int i = 0; i < str.size(); i++) { if (str[i] == '\\') //注意用反义字符,不然会报错 { //只要不在串尾,只会是目录 pre = insertNode(pre, str.substr(idx, i - idx), 1); idx = i + 1; //移动字符串到下一个目录,即 '\' 之后 } } if (idx < str.size()) //文件只出现在字符串尾 { pre = insertNode(pre, str.substr(idx, str.size() - idx), 0); } }

结点插入算法#

该算法的目的是向一个树结构中,在正确的位置插入新结点,是解决这个问题的核心。这里要强调一下返回值的重要性,如果不设置返回值,而是把目录引用进去,那么回到调用函数的时候需要自行将指针移动到当前目录,更为繁琐,好的解法是将插入后的接点作为所在的目录,以此为返回值返回函数调用的位置。容易出错的地方是若插入位置是目录的长子结点的话,直接通过前驱指针的后继来操作会插在错误位置导致断链,因此需要设置当前位置指针和前驱指针,就可以规避这个问题。

伪代码#

代码实现#

Copy Highlighter-hljs
CSTree insertNode(CSTree t, string str, int flag) //核心在此 { CSTree a_node = new CSNode; CSTree pre = t, ptr; a_node->data = str; //初始化新结点 a_node->firstchild = a_node->rightsib = NULL; a_node->flag_file = flag; if (t->firstchild == NULL) //所在目录没孩子,直接插入结点 { t->firstchild = a_node; return t->firstchild; } ptr = t->firstchild; //由于根结点本身插入时,是插在长子位,因此另外设置 pre 当前驱结点,ptr 当 pre 的后继,比较好写 while (ptr != NULL && ((ptr->flag_file > a_node->flag_file) || (ptr->flag_file == a_node->flag_file && str > ptr->data))) { pre = ptr; ptr = ptr->rightsib; } //要先判空,不然有段错误 if (ptr == NULL) //无处可插入,插在链尾 { a_node->rightsib = pre->rightsib; pre->rightsib = a_node; return a_node; //接下来以 a_node 为根目录操作 } else if (ptr->data == a_node->data && ptr->flag_file == a_node->flag_file) //目录或文件已存在(第三版就是因为这个出问题) { delete a_node; //把申请的新结点打掉 return ptr; //接下来在已有的 ptr 目录下操作 } else //找到了应该插入的位置 { if (pre->data == t->data) //插在根目录的长子位 { a_node->rightsib = pre->firstchild; pre->firstchild = a_node; } else //正常插入 { a_node->rightsib = pre->rightsib; pre->rightsib = a_node; } return a_node; //接下来以 a_node 为根目录操作 } }

打印目录树#

我们已经知道输出结点的顺序就是先序遍历二叉树的顺序,因此我们只需要添加个缩进的机制就能实现。

Copy Highlighter-hljs
void PreOrderTraverse(CSTree T, int space) //从博客上拿来的遍历函数 { //因为要输出空格,稍微改装下 if (T == NULL) return; for (int i = 0; i < space; i++) { cout << " "; } cout << T->data << endl; //前序遍历 PreOrderTraverse(T->firstchild,space + 2); //下一层多两个空格 //cout << T->data << " " ; //中序遍历 PreOrderTraverse(T->rightsib,space); //兄弟结点不需要多空格 //cout << T->data << " " ; //后序遍历 }

主函数#

测试样例#

输入样例#

Copy Highlighter-hljs
15 b c\ ab\cd a\bc ab\d a\d\a a\d\z\ b\ c ab\cd\e a\bc\f ab\d\g a\d\a\h a\d\z

输出样例#

Copy Highlighter-hljs
root a bc f d a h z a z bc ab cd e d g cd d b c b c
posted @   乌漆WhiteMoon  阅读(1872)  评论(3编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
CONTENTS