数据结构课程设计 树形目录结构

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

数据结构课程设计实验
树的遍历,文件目录结构的显示
实验报告
一、简介
树型结构是一类十分重要的非线性结构,它可以很好地描述客观世界中广泛存在的具有分支关系或层次特性的对象,如操作系统的文件构成、人工智能搜索算法的模型表示以及数据库系统的信息组织形式等。

文件的目录结构是树型结构在计算机操作系统的典型应用。

通过树型结构可以直观且清晰的表明操作系统中的文件组织结构。

用户可通过树型结构显示的文件目录列表找到自己想访问的内容。

本实验的要求在给出Unix下目录和文件信息的前提下,编程实现将其排列成一棵具有一定缩进的树。

具体要求如下:
输入要求
输入数据包含几个测试案例。

每一个案例由几行组成,每以行都代表了目录树的层次结构。

第一行代表目录的根节点。

若是目录节点,那么它的孩子节点将在第二行中被输出,同时用一对圆括号“()”界定。

同样,如果这些孩子节点中某一个也是目录的话,那么这个目录所包含的内容将在随后的一行中列出,由一对圆括号将首尾界定。

目录的输入格式为:*nams size,文件的输入格式为:name size,其中*代表当前节点是目录,name代表文件或目录的名称,由一串长度不大于10的字符组成,并且name字符串中不能含有‘(’,‘)’,‘[’,‘]’和‘*’。

size是该文件/目录的大小,为一个大于0的整数。

每一个案例中最多只能包含10层,每一层最多有10个文件/目录。

输出要求
对每一个测试案例,输出时要求:第d层的文件/目录名前面需要插入8*d个空格,兄弟节点之间要在同一列上。

不要使用Tab(制表符)来统一输出的缩进。

每一个目录的大小(size)是它所包含的所有子目录和文件大小以及它自身大小的总和。

有输入/输出样例如下:
输入样例:
*/usr 1
(*mark 1 *alex 1)
(hw.c 3 *course 1)(hw.c 5)
树的遍历,文件目录结构的显示
(aa.txt 12)
*/usr 1
()
输出样例:
|_*/usr[24]
|_*mark[17]
| |_hw.c[3]
| |_*course[13]
|_aa.txt[12]
|_*alex[6]
|_hw.c[5]
|_*usr[1]
因此,实验任务如下:
(1)读入给定的Unix目录和文件信息。

(2)建立树型链表以表示目录和文件结构,同时重新计算目录大小
(3)以树型结构输出文件信息。

二、设计说明
2.1、数据结构实现
目录结构是一种典型的树形结构,可以选择孩子兄弟双亲链表来储存树的结构。

孩子兄弟双亲链表是一种典型的树的存储结构。

在该表示方法中,链表中节点的三个指针域分别指向该节点的父亲、第一个孩子节点和下一个兄弟节点,分别命名为Parent域,FirstChild域和NextSibling域。

在孩子兄弟双亲链表表示法中,节点形式统一,节点间的联系比较简洁。

同时,在这种存储结构上容易实现树数据结构的大多数运算。

因此,孩子兄弟双亲链表表示是树的一种实用的储存结构。

其节点形式如下:
*FirstChild data*NextSibling*Parent
本题中采用面向对象的方式实现树型结构。

构建Tree类,将所有的目录作为Tree类的对象。

Tree类定义如下:
class Tree{
string Name; //树的根节点名称
int Size; //树的大小,用于统计树本身及其子数大小的总和
Tree* FirstChild; //指向他的第一个孩子节点
Tree* NextSibling; //指向下一个兄弟节点
Tree* Parent; //指向父节点
1
数据结构课程设计
public:
Tree (string Name=””,int Size=0);//构造函数
void parse(); //根据输入数据建立树型结构
void reSize(); //重新统计树节点的大小
void outPut(); //输出函数
~Tree(); //析构函数
};
2.2、主要函数的实现
在Tree类中共定义了5个函数,即构造树(Tree()构造函数),销毁树
(~Tree()析构函数),目录大小计算(reSize()函数),建立树型链表结构(parse()函数)和树型结构输出(outPut()函数)。

下面分别说明如下:
2.2.1构造函数
构造函数的作用是建立一个只有一个节点的树,其三个指针域均为空,以方便接下来目录结构的读入和树型链表的构建。

函数实现如下:
Tree::Tree(string Name, int Size){
this->Name = Name;
this->Size = Size;
FirstChild = NULL; //FirstChild域置空
NextSibling = NULL; //NextSibling域置空
parent = NULL; //Parent域置空
}
2.2.2析构函数
析构函数的作用是删除同一个根节点下的各个子节点,以释放空间。

函数实现如下:
Tree::~Tree()
{
Tree* temp;
Tree* temp1;
temp = FirstChild;
while(temp != NULL)
{
temp1 = temp;
temp = temp->NextSibling;
delete temp1;
}
2
树的遍历,文件目录结构的显示
3 }
2.2.3建立树型链表结构(parse()函数)
根据输入来确定树型关系时,首先读取根节点目录/文件名和大小值,并根据这些节点信息建立一个新的节点;然后读入后面各行的信息,对于同一括号中的内容,即具有相同父节点(在同一目录中的)接点建立兄弟关联。

这个函数实际上是采用层次遍历建立树型链表结构。

这里定义一个Tree*类型的数组treeArray[],此数组存放目录的节点信息。

整形变量head 和rear 用来标记当前节点的父节点位置。

每处理完一对括号(即同一目录中的所有目录/文件已被处理完),head 值加1,下一对待处理括号的父节点在treeArray[]数组中应当后移一个位置。

若当前处理节点是目录类型,则放置在treeArray[]数组中,rear 作为数组的下标变量,加入一个目录节点信息rear 便加1。

若是文件类型,则需按照Name 和Size 建立一个数的节点,并和head 所指的父节点建立关联(即表明此文件所存储的目录),但不用放进treeArray[]数组中。

此函数可形成如下的数据结构:
若有
*/admin 1
(*usr1 1 *usr2 1)
(file1.c 3 *folder 1)(file2.c 5)
则有下图:
treeArray[]
treeArray[]中存储的为目录列表。

该函数实现如下:
void Tree::parse()
{
Tree* temp;
string line;
string name;
int size;
usr2 /admin usr1 file1.c folder file2.c
FirstChild FirstChild FirstChild Parent Parent Parent Parent NextSibling NextSibling
数据结构课程设计
while(getline(infile,line,'\n')) //读入行
{
startPos = 0;
while(1)
{
s = getSubDir(line, &startPos); //获取一对()间字符串
int i = 1;
skipWhiteSpace(s, &i); //跳过字符串s中多余的空格
if(s[i] != ')')
{
skipWhiteSpace(s,&i);
name = getName(s,&i); //获取目录/文件名称
skipWhiteSpace(s,&i);
size = getSize(s,&i); //获取目录/文件大小
temp = treeArray[head%100]->FirstChild = new Tree(name,size); //获取第一个孩子节点
temp->parent = treeArray[head%100];
if(name[0] == '*')
treeArray[(rear++)%100] = temp; //目录则rear+1
skipWhiteSpace(s,&i);
}
while(s[i] != ')')
{
skipWhiteSpace(s,&i);
name = getName(s,&i);
skipWhiteSpace(s,&i);
size = getSize(s,&i);
temp->NextSibling = new Tree(name,size);
//获取下一个兄弟节点
skipWhiteSpace(s,&i);
temp = temp->NextSibling;
temp->parent = treeArray[head%100];
if(name[0] == '*')
treeArray[(rear++)%100] = temp; //目录则rear+1 }
head ++; //一对()扫描完毕
if((unsigned int)startPos >= line.length())
break;
4
树的遍历,文件目录结构的显示 5
}
if(head == rear) //只有一个根节点时,head=rear
break;
} } 2.2.4目录大小计算(reSize()函数)
输入数据中,目录大小的初始化值一般为1,而目录真正的大小为其自身大小与包含的所有子目录/文件大小的和。

因此在计算目录大小时,应当遍历下面所有的文件和子目录。

在此采用递归的后根遍历方法。

此外需注意的是,在采用孩子兄弟双亲链表表示树时,父目录下所有的子目录和自文件都在其左子树上,因此遍历的时候只需遍历其左子树即可。

例如有
*/admin 1
(*usr1 1 *usr2 1)
*/admin2 1
显然其admin 所有子目录都在其左子树上,其右子树上为与之平级的目录(兄弟节点)。

该函数实现如图:
void Tree::reSize()
{
Tree* temp = this;
if(temp->FirstChild != 0){ //后根遍历左子树
temp = temp->FirstChild;
while(temp != 0){
temp->reSize();
Size += temp->Size;
temp = temp->NextSibling;
}
} } /admin *usr1 *usr2 /admin2
数据结构课程设计
2.2.5树型结构输出(outPut()函数)
很显然,输出是对树的先根遍历过程。

为了完成对于目录的树型输出,兄弟目录之间需要相同的缩进,用‘|’上下相连,而父子目录或父目录和子文件间需要设置正确的缩进,子目录或子文件比父目录右缩进8个空格。

设置一个标志数组flag[11](题目规定每个目录下最大层次数为10),当前Tree* temp指针所指向的节点如果有兄弟节点,则置flag为1,否则为0;并由此节点反复查询它的祖先节点;遇到flag[]=0则输出”“,flag[]=1则输出”|“,表明是兄弟节点。

这样可以保证兄弟节点间有相同缩进,而子节点比父节点向右缩进8个字符。

该函数实现如下:
void Tree::outPut()
{
Tree* temp; //用来指向当前结点的祖先结点
Tree* temp1;
bool flag[11];
int i;
outfile.open("output.txt",ios::app); //结果保存至output.txt中
if(!outfile){
cout<<"cannot append the output file.\n";
exit(0);
}
if(!checkName(Name)){
cout<<"input error!--"<<Name<<endl;
exit(0);
}
outfile<<"|_"<<Name<<"["<<Size<<"]\n";
outfile.close();
temp1= FirstChild; //用来指向当前结点的子结点
while(temp1 != NULL)
{
outfile.open("output.txt",ios::app);
if(!outfile){
cout<<"cannot append the output file.\n";
exit(0);
}
i = 0;
temp = temp1;
while(temp->parent != NULL)
6
树的遍历,文件目录结构的显示
{
if(i>=10){
cout<<"input error!--dictionary contains more than 10 levels."<<endl;
exit(0);
}
temp = temp->parent;
if(temp->NextSibling != NULL)
flag[i++] = true; //即flag[]为1,兄弟节点
else
flag[i++] = false; //即flag[]为0,父节点
}
while(i--)
{
if(flag[i] == true)
outfile<<"| "; //兄弟节点输出
else
outfile<<" "; //父节点输出
}
outfile.close(); //关闭文件
temp1->outPut();
temp1 = temp1->NextSibling;
}
}
三、测试结果
本试验提供了四组输入样例,其中一组为只有根节点,以测试在极端情况下算法的稳定性,一组为有两个目录,每个目录下有不超过5个目录/文件,测试算法在一般情况下的运行情况,一组为有3个目录,其中部分目录名为长度为10的整数,部分目录下嵌套10层子目录/文件,以测试在样本容量较大情况下的算法稳定性,最后一组为错误的数据,以测试程序的健壮性。

测试结果如下:
7
数据结构课程设计
8
树的遍历,文件目录结构的显示
9
文件信息用树形结构表示出来。

四、分析与探讨
本程序应用了面向对象的设计思想,将每个目录作为Tree类的对象加以实现,使所有对于目录的操作具有较好的通用性和可移植性。

同时,对于反复使用的checkName()、skipWhiteSpace()、getSubDir()等函数,程序将其单独声明以便于在各个函数中调用执行。

最大限度的减少了编程工作量,符合模块化、结构化的程序设计思想。

在写作过程中对相应语句标明了语句注释,方便阅读。

此外,在程序中大量应用了树数据结构的基本操作,如函数parse()中用到了树的层序遍历:
while(s[i] != ')')
{
skipWhiteSpace(s,&i);
name = getName(s,&i);
skipWhiteSpace(s,&i);
size = getSize(s,&i);
temp->NextSibling = new Tree(name,size);
skipWhiteSpace(s,&i);
temp = temp->NextSibling;
temp->parent = treeArray[head%100];
if(name[0] == '*')
treeArray[(rear++)%100] = temp;
}
在函数reSize()中应用到了递归的后序遍历:
while(temp != 0){
temp->reSize();
Size += temp->Size;
temp = temp->NextSibling;
}
在函数outPut()中应用到了树的先序遍历:
while(temp->parent != NULL)
{
if(i>=10){
cout<<"input error!--dictionary contains more than 10 levels."<<endl;
exit(0);
}
temp = temp->parent;
if(temp->NextSibling != NULL)
flag[i++] = true;
else
flag[i++] = false;
}
这样可以在树数据结构基础基础算法的基础上稍加改进即可实现实验所需求的功能。

很显然,程序仍有许多不完善的地方。

例如程序只可识别10层嵌套的目录/
文件,对于超过十层的目录/文件程序便无法识别。

此外,对于文件名超过10的文件处理不够完善,会出现下列的错误:
输入:
*/usr 1
(*admin 1 *s1 1)
(hw6232423.exe 3 *course 1) (hw.c 5)
(aa.txt 12)
输出:
The result is as follows:
|_*/usr[24]
|_*admin[17]
|
即程序在执行到读取串”hw6232423.exe”时自动终止,无法输出正确的目录/文件结构。

此外,对于可能出现的输入错误,程序并未有告警。

虽不会导致程序崩溃,但是与用户的交互性不强。

本程序在Dev C++ 4.9.9.2版本和Visual C++ 6.0版本上调试通过。

附录源代码
Tree.cpp
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
string s = "";
int startPos = 0;
ofstream outfile;
ifstream infile;
class Tree{
string Name; //树的根结点名称
int Size; //树的大小,用于统计这棵树本身及其包含的所以子树大小的总和
Tree* FirstChild; //指向它的第一个孩子结点
Tree* NextSibling; //指向它的下一个兄弟结点
Tree* parent; //指向双亲结点
public:
Tree(string Name = "", int Size = 0); //构造函数
void parse(); //根据输入数据来建立树形结构
void reSize(); //重新统计树结点的大小void outPut(); //输出树形结构
~Tree(); //析构函数
};
Tree* treeArray[100];
int head = 0, rear = 0;
Tree::Tree(string Name, int Size){
this->Name = Name;
this->Size = Size;
FirstChild = NULL;
NextSibling = NULL;
parent = NULL;
}
Tree::~Tree()
{
Tree* temp;
Tree* temp1;
temp = FirstChild;
while(temp != NULL)
{
temp1 = temp;
temp = temp->NextSibling;
delete temp1;
}
}
void Tree::reSize()
{
Tree* temp = this;
if(temp->FirstChild != 0){
temp = temp->FirstChild;
while(temp != 0){
temp->reSize();
Size += temp->Size;
temp = temp->NextSibling;
}
}
}
bool checkName(string s)
{
if(s[0]!='*' && s.length() > 10)
return false;
if(s[0]=='*' && s.length() > 11)
return false;
if(s[0]!='*' && (s[0]=='(' || s[0]==')' || s[0]=='[' || s[0]==']'))
return false;
for(int i=1;i<s.length();i++){
if(s[i]=='*' || s[i]=='(' || s[i]==')' || s[i]=='[' || s[i]==']')
return false;
}
return true;
}
void Tree::outPut()
{
Tree* temp; //用来指向当前结点的祖先结点
Tree* temp1;
bool flag[11]; //用来标志输出缩进、层次情况的数组int i;
outfile.open("output.txt",ios::app);
if(!outfile){
cout<<"cannot append the output file.\n";
exit(0);
}
if(!checkName(Name)){
cout<<"input error!--"<<Name<<endl;
exit(0);
}
outfile<<"|_"<<Name<<"["<<Size<<"]\n";
outfile.close();
temp1= FirstChild; //用来指向当前结点的子结点while(temp1 != NULL)
{
outfile.open("output.txt",ios::app);
if(!outfile){
cout<<"cannot append the output file.\n";
exit(0);
}
i = 0;
temp = temp1;
while(temp->parent != NULL)
{
if(i>=10){
cout<<"input error!--dictionary contains more than 10 levels."<<endl;
exit(0);
}
temp = temp->parent;
if(temp->NextSibling != NULL)
flag[i++] = true;
else
flag[i++] = false;
}
while(i--)
{
if(flag[i] == true)
outfile<<"| ";
else
outfile<<" ";
}
outfile.close();
temp1->outPut();
temp1 = temp1->NextSibling;
}
}
void skipWhiteSpace(string& s, int* i) {
while(s[*i] == '\t' || s[*i] == ' ')
(*i)++;
}
string getSubDir(string& line, int* startPos) {
string res = "";
skipWhiteSpace(line,startPos);
while(line[*startPos] != ')')
res += line[(*startPos)++];
res += line[(*startPos)++];
skipWhiteSpace(line, startPos);
return res;
}
int stringToNum(string s)
{
int num = 0;
unsigned int i = 0;
while(i < s.length())
{
num *= 10;
num += s[i++] - '0';
}
return num;
}
string getName(string& s, int* i)
{
string name = "";
while(s[*i] != ' ' && s[*i] != '\t')
name += s[(*i)++];
return name;
}
int getSize(string&s, int* i)
{
string size = "";
while((unsigned int)(*i) < s.length() && s[*i] != ' ' && s[*i] != '\t' && s [*i] != ')')
size += s[(*i)++];
return stringToNum(size);
}
void Tree::parse()
{
Tree* temp;
string line;
string name;
int size;
while(getline(infile,line,'\n'))
{
startPos = 0;
while(1)
{
s = getSubDir(line, &startPos);
int i = 1;
skipWhiteSpace(s, &i);
if(s[i] != ')')
{
skipWhiteSpace(s,&i);
name = getName(s,&i);
skipWhiteSpace(s,&i);
size = getSize(s,&i);
temp = treeArray[head%100]->FirstChild = new
Tree(name,size);
temp->parent = treeArray[head%100];
if(name[0] == '*')
treeArray[(rear++)%100] = temp;
skipWhiteSpace(s,&i);
}
while(s[i] != ')')
{
skipWhiteSpace(s,&i);
name = getName(s,&i);
skipWhiteSpace(s,&i);
size = getSize(s,&i);
temp->NextSibling = new Tree(name,size);
skipWhiteSpace(s,&i);
temp = temp->NextSibling;
temp->parent = treeArray[head%100];
if(name[0] == '*')
treeArray[(rear++)%100] = temp;
}
head ++;
if((unsigned int)startPos >= line.length())
break;
}
if(head == rear)
break;
}
}
int main()
{
Tree* fileTree;
string s;
string name;
int size;
outfile.open("output.txt");
if(!outfile){
cout<<"cannot open the output file!\n";
exit(0);
}
数据结构课程设计outfile<<"The result is as follows:\n";
outfile.close();
infile.open("input.txt",ios::out);
if(!infile){
cout<<"cannot open the input file!\n";
exit(0);
}
while(getline(infile,s,'\n'))
{
int i = 0;
skipWhiteSpace(s, &i);
name = getName(s,&i);
skipWhiteSpace(s,&i);
size = getSize(s,&i);
fileTree = new Tree(name, size);
if(name[0] == '*')
{
treeArray[rear++] = fileTree;
fileTree->parse();
}
fileTree->reSize();
fileTree->outPut();
delete fileTree;
}
infile.close();
return 0;
}
20。

相关文档
最新文档