在ZIP归档文件中,保留着所有压缩文件和目录的相对路径和名称。当使用WinZIP等GUI软件打开ZIP归档文件时,可以从这些信息中重建目录的树状结构。请编写程序实现目录的树状结构的重建工作。
输入格式:
输入首先给出正整数N(≤104),表示ZIP归档文件中的文件和目录的数量。随后N行,每行有如下格式的文件或目录的相对路径和名称(每行不超过260个字符):
- 路径和名称中的字符仅包括英文字母(区分大小写);
- 符号“\”仅作为路径分隔符出现;
- 目录以符号“\”结束;
- 不存在重复的输入项目;
- 整个输入大小不超过2MB。
输出格式:
假设所有的路径都相对于root目录。从root目录开始,在输出时每个目录首先输出自己的名字,然后以字典序输出所有子目录,然后以字典序输出所有文件。注意,在输出时,应根据目录的相对关系使用空格进行缩进,每级目录或文件比上一级多缩进2个空格。
输入样例:
输出样例:
这个题其实不需要建一颗多叉树,只需要用何老师教的左孩子右兄弟表示法就可以,数据结构就是二叉链表,最后在用二叉树的前序遍历即可解决这个问题。当然具体怎么建树是一个难点,我先把代码贴在这里,过几天再来写这个题的具体分析思路和一些坑。
-------------------------------------------------------------------------------------------------------------------------------
核心的建树思路:
先提些注意事项,目录的插入优先级一定比文件高,其实目录就相当于非叶结点,文件就相当于叶结点,所以文件不管其字典序多小,和目录比它永远要往后挪。
还有一点是我们每次都是只处理一行字符串,每次都是从根结点root开始逐一插入这一行的目录和文件。
最后一点是插入优先级相同的字典序在前。
下面是以案例为例子的一个具体的建树过程,看完这个过程如果你会写程序了就赶紧去写程序吧!
总结一下整个建树过程是这样的:
先建立一颗仅有根结点root的二叉树。
然后读入一行字符串。注意到一行字符串可以有若干个目录但至多只有一个文件。
先要设置一个指针pos指向root。
根据这个规律我们首先在字符串里面找到第一个目录,然后再再以pos为根的二叉树里寻找是否存在这个目录,如果有的话我们用pos记录这个已经存在于二叉树里的目录的地址。如果二叉树里不存在这个目录,那么我们来寻找这个目录应该放置在二叉树中的位置。寻找过程是从根结点的孩子开始循链往右走,注意我们是一路往右走到底。每次我们和链上的一个结点Node的优先级进行比较(所谓优先级我们在这里规定目录优先级为1,文件优先级为0,1比0大所以目录优先级高),如果待插入优先级比链上这个结点大或者优先级相同但待插入结点字典序较小的话就直接插入链上这个结点Node的前面即可,优先级比链上这个结点小或者优先级相同但是字典序大于链上这个结点Node的话我们就继续沿着这条链往后寻找。最极端的情况是找到了链的末尾,那么链的末尾就是我们带插入元素的位置。 最后把第一个目录插入二叉树中同时更新pos为插入的位置。
然后我们再来读入第二个目录,然后重复上面的步骤。
继续读入第三个目录。。。
当所有目录读完后,我们要检查最后一个目录后面有没有文件,如果有的话,把它插入以pos为根的子树中,要注意文件跟目录不同,目录有可能已经在子树中存在,所以我们要先检查目录是否存在于二叉树中,但是文件是不需要检查的,我们只要直接把它插入到以pos为根的子树中即可。
到此为止我们就完成了对一行字符串的处理了。这里面有个小细节是怎么从字符串中截取目录或文件,我们只要用一下substr()函数就可以了。
于是我们以同样的方式处理下一行字符串,直到所有的字符串都被处理完毕。
下面这个图模拟了如何插入一个目录和文件的过程:
程序框架:
int main()
{
建立一颗只有根结点root的二叉树;
for(i=0; i<N; i++)
{
读入一行字符串str;
设置一个指针pos指向root;
while(str中还有目录还没插入二叉树中)
{
读入一个目录;
将目录插入以pos为根的子树中,并把pos更新为插入的地址;
}
if(有文件未读入)
{
读入文件;
把文件插入以pos为根结点的子树;
}
}
前序遍历以root为根结点的二叉树;
return 0;
}
代码:
#include<stdio.h>
#include<string.h>
#include<string>
#include<stack>
#include<iostream>
using namespace std;
struct TreeNode//左孩子右兄弟的二叉树表示法
{
string str;
TreeNode* child;//左孩子
TreeNode* sibling;//右兄弟
int priority;//priority==0表示为文件,priority==1为目录,目录的插入优先级比文件高
};
typedef TreeNode* Position;
typedef Position BinTree;
Position createNode(string s, int priority)//创建一个新结点并初始化
{
Position Node = new TreeNode;
Node->str = s;
Node->child = Node->sibling = NULL;
Node->priority = priority;
return Node;
}
Position insert(BinTree root, string s, int priority)//插入结点函数,把结点作为root的孩子插入,并返回插入的位置
{
if(root->child == NULL)//如果root的根本没有儿子的话,那么直接插入在root儿子的位置即可。
{
root->child = createNode(s,priority);
return root->child;
}
Position tmpNode = root->child,father = root;
//没有return,说明root有儿子,开从root的儿子开始循链找插入位置
//如果待插入结点的优先级小于tmpNode的优先级,或者tmpNode的优先级和待插入结点优先级一致,但待插入结点的字典序大于tmpNode的字典序,要继续循链寻找。
//注意因为循链寻找的的极端过程是找到了链的最右边,即可能tmpNode可能会为NULL,所以要加入判断语句tmpNode!=NULL,即当找到链的末尾时就要退出,否则会产生段错误。
while(tmpNode!=NULL&&((priority<tmpNode->priority)||(tmpNode->priority==priority&&s>tmpNode->str)))
{
father = tmpNode;
tmpNode = tmpNode->sibling;
}
//当我们退出循环时有3种情况,1、找到了链的末尾,说明结点应该插入链的末尾处;2、待插入的目录已经存在于二叉树中,我们直接返回目录所在位置即可,无需插入;
//3、找到了应该插入的位置。这3中情况分别对应下面的if else语句。
if(tmpNode==NULL)//1、找到了链的末尾,说明结点应该插入链的末尾处;
{
Position Node = createNode(s,priority);
Node->sibling = father->sibling;
father->sibling = Node;
return Node;
}
if(tmpNode->priority==priority&&s==tmpNode->str)//2、待插入的目录已经存在于二叉树中,我们直接返回目录所在位置即可,无需插入;
return tmpNode;
else//3、找到了应该插入的位置。
{
Position Node = createNode(s,priority);
if(father->str == root->str)//但这里又有一个细节,插入位置是在root的child的位置和root的child的sibling的位置的插入方法是不一样的。
{
Node->sibling = father->child;
father->child = Node;
}
else
{
Node->sibling = father->sibling;
father->sibling = Node;
}
return Node;
}
}
void PreorderTraversal(BinTree BT,int layer)//前序遍历输出,注意需要一个参数layer来控制缩进,递归和非递归都可以,非递归版本我也写了但是没有递归这么漂亮,想要非递归版本可以给我留言
{
int i;
int childlayer,siblinglayer;
if(BT)
{
for(i=0; i<layer; i++)
printf(" ");
childlayer = layer+1;
siblinglayer = layer;
cout<<BT->str<<endl;
PreorderTraversal(BT->child,childlayer);
PreorderTraversal(BT->sibling,siblinglayer);
}
}
int main()
{
int N,i,bgn,end,j;
string str;
Position pos;
BinTree root = createNode("root",1);//建立只有一个根结点的二叉树,根结点也是目录所以优先级为1
scanf("%d\n",&N);
for(i=0; i<N; i++)
{
getline(cin,str); pos = root; bgn = end = 0;//bgn记录目录或文件的首字符位置,end记录目录或文件最后一个字符后面一个字符的位置
for(j=0; j<str.length(); j++)
{
if(str[j] == '\\')
{
end = j;
pos = insert(pos,str.substr(bgn,end-bgn),1);
bgn = end+1;
}
}
if(str[bgn]!='\0')//读完所有目录后,判断字符串中是否还有文件,如果有,读入文件
{
insert(pos,str.substr(bgn,str.length()-bgn),0);
}
}
PreorderTraversal(root,0);//前序遍历输出
return 0;
}