一.题目:
利用二叉树简单理解文件夹创建的过程。
二.代码:
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
//二叉树结构体
typedef struct BiTNode
{
//1.结点即文件夹名称
char nameFileNode[50];
//2.左、右孩子指针即子目录
struct BiTNode *lchild;
struct BiTNode *rchild;
//3.父指针即父级目录
struct BiTNode *parent;
}BiTNode,*BiTree;
//单个文件夹在顺序表中的数据
typedef struct
{
//1.文件夹名称
char nameFile[50];
//2.文件夹的父级路径的索引
int parentIndex;
}FileData;
//存储二叉树即文件夹的顺序表->长度采用动态变化
typedef struct
{
//1.指向动态数组的指针->表示连续存储的空间
FileData *data;
//2.顺序表中当前文件夹个数
int numFile;
//3.动态数组当前数组最大容量
int capacity;
}FileList;
/*---------------------------------------赵旭文---------------------------------------*/
//求文件夹二叉树的树高的函数
int treeDepth(BiTree T)
{
if(T==NULL)
{
return 0;
}
else
{
int l = treeDepth(T->lchild);
int r = treeDepth(T->rchild);
return l > r ? l+1 : r+1;
}
}
//查找父级目录在文件夹顺序表中的位置的函数
/*形参需要文件夹顺序表、父指针指向的文件夹*/
/*返回父级目录在文件夹顺序表中的索引*/
int findParent(FileList list,BiTNode *parent)
{
//特殊情况即根结点->只有根结点的父指针为NULL
if(parent == NULL)
{
return -1; // 根节点的父节点索引为-1
}
//1.遍历文件夹顺序表查找是否有与父指针指向的文件夹同名的文件夹
for( int i=0 ; i<list.capacity ; i++ )
{
//2.判断当前索引上的文件夹与父指针指向的文件夹是否同名
int result = strcmp( list.data[i].nameFile , parent->nameFileNode );
//3.结果
if(result==0)
{
//4.查找成功,返回索引
return i;
}
}
//5.查找失败,返回-1
return -1;
}
/*---------------------------------------赵旭文---------------------------------------*/
/*---------------------------------------黄黄---------------------------------------*/
//添加文件夹到文件夹顺序表的函数
/*形参包括文件夹顺序表、插入的文件夹名称(插入到顺序表)、文件夹二叉树(用来求扩容常量)、插入的文件夹(找父级路径)*/
void InsertFileData(FileList &list,char name[50],BiTree T,BiTNode file)
{
//1.把文件夹放入文件夹顺序表的最后一位(刚好就是list.numFile,比如0个文件夹时新添加一个就在0索引)
strcpy( list.data[ list.numFile ].nameFile , name );
//2.补全刚放入的文件夹的父级目录的索引
/*2.1.调用函数findParent查找父级目录在文件夹顺序表中的索引*/
int index= findParent( list , file.parent );
/*2.2.赋值*/
list.data[ list.numFile ].parentIndex = index;
//3.文件夹顺序表的文件夹数量加1
list.numFile++;
//4.文件夹顺序表扩容
/*4.1.求文件夹二叉树的高度*/
int height=treeDepth(T);
/*4.2.得出扩容结果*/
//list.capacity = list.capacity + pow(2,height);这里只改变了capacity,但没有重新分配内存(会出现主函数崩溃),正确如下:
//得出新的扩容结果并记录
int newCapacity = list.capacity + pow(2, height);
/*调用realloc函数,第一个参数就是先前获得的内存块,第二个参数就是新扩容的内存块,
如果需要还会复制旧内存块的内容*/
FileData *newData = (FileData*)realloc( list.data, sizeof(FileData) * newCapacity );
//判断
if(newData!=NULL)
{
//此时成功分配
list.data = newData; //记录新的扩容结果
list.capacity = newCapacity; //记录扩容后的长度
}
}
// 创建文件夹(结点)即插入结点到二叉树的函数
/*形参需要文件夹二叉树、新插入的文件夹名称、文件夹顺序表*/
void creatFile(BiTree &T, char inputFile[50],FileList &list)
{
//1.判空
if(T == NULL) return;
//2.在文件夹二叉树中找到要插入的位置,判断是否创建在当前文件夹的子目录下
printf("当前文件夹:%s \n", T->nameFileNode);
printf("请选择创建位置(1.左子目录 2.右子目录 3.取消创建):");
int result;
scanf("%d", &result);
//3.判断
if(result == 1)
{
//4.创建在左目录下
if(T->lchild == NULL) //左子目录为空,可直接创建
{
//4.1.先给T的左孩子分配内存空间
T->lchild = (BiTree)malloc(sizeof(BiTNode));
//4.2.判断
if( T->lchild == NULL) /*内存分配失败*/
{
printf("内存分配失败!\n");
return;
}
else /*内存分配成功*/
{
strcpy(T->lchild->nameFileNode, inputFile); //初始化新节点
T->lchild->lchild = NULL; //设置左指针
T->lchild->rchild = NULL; //设置右指针
T->lchild->parent = T; //设置父指针
printf("文件夹 '%s' 创建成功!\n", inputFile);
InsertFileData(list, inputFile, T , *(T->lchild)); //添加到顺序表中.第四个形参是插入的文件夹即左孩子上
}
}
else //左子目录不为空,需递归
{
printf("左子目录已存在,递归创建...\n");
creatFile(T->lchild, inputFile , list);
}
}
else if(result == 2)
{
//5.创建在右目录下
if(T->rchild == NULL) //右子目录为空,可直接创建
{
//5.1.先给T的右孩子分配内存空间
T->rchild = (BiTree)malloc(sizeof(BiTNode));
//5.2.判断
if(T->rchild == NULL) /*内存分配失败*/
{
printf("内存分配失败!\n");
return;
}
else /*内存分配成功*/
{
strcpy(T->rchild->nameFileNode, inputFile); // 初始化新节点
T->rchild->lchild = NULL; //设置左指针
T->rchild->rchild = NULL; //设置右指针
T->rchild->parent = T; // 设置父指针
printf("文件夹 '%s' 创建成功!\n", inputFile);
InsertFileData(list, inputFile, T , *(T->rchild)); // 添加到顺序表中
}
}
else //右子目录不为空,需递归
{
printf("右子目录已存在,递归创建...\n");
creatFile(T->rchild, inputFile, list);
}
}
else
{
//6.取消创建
printf("取消创建操作。\n");
return;
}
}
/*---------------------------------------黄黄---------------------------------------*/
/*---------------------------------------青格勒---------------------------------------*/
//删除文件夹(结点)即删除二叉树中结点的函数->1次只删1个文件夹,删两个会出现父级路径和子文件夹都删了
/*形参需要文件夹二叉树、要删除的文件夹名称、文件夹顺序表*/
void deleteFile(BiTree &T, char deleteFileName[50], FileList &list)
{
// 1.首先在顺序表中查找要删除的文件夹
int deleteIndex = -1;
for(int i = 0; i < list.numFile; i++)
{
if( strcmp(list.data[i].nameFile, deleteFileName) == 0)
{
//代表查找成功
deleteIndex = i;
break;
}
}
// 2.如果未找到,直接返回
if(deleteIndex == -1)
{
printf("删除失败,文件夹 '%s' 不存在!\n", deleteFileName);
return;
}
//3.在二叉树中查找要删除的结点
BiTree deleteNode = NULL; //要删除的结点即要删除的文件夹
BiTree parentNode = NULL; //要删除的文件夹的父级路径
int isLeftChild = 0; // 0:不是左孩子 1:是左孩子
//3.1.使用层次遍历查找要删除的节点->使用到了队列->类似线性查找
BiTree queue[100]; //队列->长度为100,类型为文件夹二叉树
int front = 0, rear = 0; //front为头指针,rear为尾指针
queue[rear++] = T; //初始时访问根结点,根结点T入队->注:是先赋值,再rear++(rear++得出的rear就指向了下一个插入的位置)
//3.2.循环遍历文件夹二叉树->只要头指针小于尾指针,就说明队列里有文件夹,就需要继续处理
while(front < rear)
{
//3.2.先处理头指针,把头指针设为当前处理的文件夹current,然后front++(代表下次处理后一个文件夹)
BiTree current = queue[front++];
//3.3.对比当前处理的文件夹与要删除的文件夹是否同名
if( strcmp(current->nameFileNode, deleteFileName) == 0)
{
//3.4.找到了要删除的文件夹,赋值给deleteNode,并跳出循环
deleteNode = current;
break;
}
//3.4.此时意味着没有找到要删除的文件夹,让其左子树、右子树依次入队列进行查找
if(current->lchild != NULL)
{
//先赋值,再rear++
queue[rear++] = current->lchild;
}
if(current->rchild != NULL)
{
//先赋值,再rear++
queue[rear++] = current->rchild;
}
}
//3.5.判空
if(deleteNode == NULL)
{
printf("删除失败,在二叉树中未找到文件夹 '%s'!\n", deleteFileName);
return;
}
//4.获取父节点信息
parentNode = deleteNode->parent;
//4.1.判空
if(parentNode != NULL)
{
//4.2.父结点不为空
if(parentNode->lchild == deleteNode)
{
//4.3.意味着要删除的文件夹deleteNode在其父级路径的左孩子上
isLeftChild = 1;
}
else
{
//4.4.意味着要删除的文件夹deleteNode在其父级路径的右孩子上
isLeftChild = 0;
}
}
//5.检查要删除的节点是否有子节点
if(deleteNode->lchild != NULL || deleteNode->rchild != NULL)
{
printf("删除失败,文件夹 '%s' 包含子文件夹,请先删除子文件夹!\n", deleteFileName);
return;
}
//6.从二叉树中删除节点
//6.1.删除的是根节点
if(parentNode == NULL)
{
printf("警告:正在删除根节点!\n");
free(T);
T = NULL;
}
else //6.2.删除非根节点
{
if(isLeftChild)
{
//6.3.此时说明要删除是父级路径的左子树
parentNode->lchild = NULL;
}
else
{
//6.4.此时说明要删除是父级路径的右子树
parentNode->rchild = NULL;
}
//6.5.清空
free(deleteNode);
}
//7.从顺序表中删除对应的文件夹数据
//7.1 首先删除所有以该文件夹为父节点的子文件夹
for(int i = 0; i < list.numFile; i++)
{
//7.2.找到了孤立的文件夹
if(list.data[i].parentIndex == deleteIndex)
{
printf("警告:发现孤立的子文件夹 '%s',正在清理...\n", list.data[i].nameFile);
}
}
//7.3.移动顺序表中的文件夹,覆盖要删除的元素(删除最后1个文件夹时无需移动)
for(int i = deleteIndex; i < list.numFile - 1; i++)
{
//7.4.把后一个文件夹移动到当前文件夹
strcpy(list.data[i].nameFile, list.data[i + 1].nameFile);
//7.5.更改父级路径索引
list.data[i].parentIndex = list.data[i + 1].parentIndex;
//7.6.更新父索引:如果父索引大于删除索引,需要减1->减1才能移动指向前1位(因为删除了1个文件夹)
if(list.data[i].parentIndex > deleteIndex)
{
list.data[i].parentIndex--;
}
}
// 7.3 更新顺序表计数即顺序表中文件夹个数减1
list.numFile--;
// 8.更新所有父索引指向被删除节点的子节点->遍历查找是否有父索引等于删除索引的
for(int i = 0; i < list.numFile; i++)
{
//8.1.找到了父索引指向删除索引的
if(list.data[i].parentIndex == deleteIndex)
{
//8.2.设置为根节点或无父节点
list.data[i].parentIndex = -1;
printf("警告:文件夹 '%s' 的父文件夹已被删除,已将其父索引重置\n", list.data[i].nameFile);
}
}
printf("文件夹 '%s' 删除成功!\n", deleteFileName);
// 9.打印删除后的顺序表状态(用于调试)
printf("删除后顺序表状态:\n");
for(int i = 0; i < list.numFile; i++)
{
printf("索引%d: 名称为%s, 父索引为%d\n", i, list.data[i].nameFile, list.data[i].parentIndex);
}
}
//带交互的删除函数包装器->进入删除操作的函数
void deleteFileInteractive(BiTree &T, FileList &list)
{
//1.判空
if(list.numFile == 0)
{
printf("当前没有文件夹可删除!\n");
return;
}
//2.此时文件夹顺序表不为空,打印
printf("当前所有文件夹:\n");
for(int i = 0; i < list.numFile; i++)
{
//2.1.打印当前文件夹名称
printf("%d. %s", i + 1, list.data[i].nameFile);
//2.2.打印当前文件夹的父级路径->根结点无需打印父级路径
if(list.data[i].parentIndex != -1)
{
printf(" (父文件夹: %s)", list.data[list.data[i].parentIndex].nameFile);
}
printf("\n");
}
//3.输入要删除的文件夹的名称
printf("请输入要删除的文件夹名称:");
char deleteFileName[50];
scanf("%s", deleteFileName);
//4.调用deleteFile函数进行删除
deleteFile(T, deleteFileName, list);
}
/*---------------------------------------青格勒---------------------------------------*/
/*---------------------------------------吴佳峻---------------------------------------*/
//查找文件夹(结点)的函数
/*形参需要文件夹顺序表、要查找的文件夹的名称*/
void findFile(FileList list,char findFileName[50])
{
//1.遍历顺序表查找是否存在
for( int i = 0; i < list.numFile; i++ )
{
//2.记录对比结果
int result = strcmp( list.data[i].nameFile , findFileName);
//3.判断
if(result == 0) /*查找成功->找出当前文件夹完整路径*/
{
printf("找到文件夹 '%s'\n", findFileName);
// 3.1.构建路径栈->打印完整路径,用栈,因为最后找到磁盘,但磁盘先打印,为栈
char pathStack[100][50]; // 存储路径组件
int stackTop = 0;
// 3.2.当前文件夹入栈
strcpy( pathStack[stackTop], list.data[i].nameFile);
stackTop++;
// 3.3.回溯父路径
int fatherIndex = list.data[i].parentIndex; //记录父级文件夹索引
while(fatherIndex != -1) //等于-1时意味着找到根目录,停止循环
{
strcpy( pathStack[stackTop], list.data[fatherIndex].nameFile);
stackTop++;
fatherIndex = list.data[fatherIndex].parentIndex;
}
// 3.4.从根节点开始拼接完整路径
printf("完整路径: ");
for(int j = stackTop - 1; j >= 0; j--) //从栈顶开始
{
printf("%s", pathStack[j]);
if(j > 0) printf("\\");
}
printf("\n");
return;
}
}
// 4.查找失败
printf("查找失败,该文件夹 '%s' 不存在\n", findFileName);
}
//初始化文件夹顺序表的函数
void initFileList(FileList *list, int capacity)
{
//1.创建连续的存储空间
list->data = (FileData*)malloc(sizeof(FileData) * capacity);
//2.初始时顺序表没有任何文件夹
list->numFile = 0;
//3.初始动态数组当前最大容量
list->capacity = capacity;
}
//初始化文件夹二叉树的函数
void initBiTree(BiTree &T,FileList &list) //不是BiTree *T,而是BiTree &T,传地址
{
//1.申请根结点存储空间->根结点为结点,用BiTNode申请空间,最终要是BiTree型即树
T = (BiTree)malloc( sizeof(BiTNode) );
//2.根结点赋值
strcpy( T->nameFileNode , "D盘" );
//3.一开始只有根结点,根结点的初始左、右孩子指针都为NULL
T->lchild = NULL;
T->rchild = NULL;
//4.根结点的父指针指向NULL
T->parent = NULL;
//5.把根结点放入文件夹顺序表
/*对于根结点需要特殊处理,在InsertFileData函数中第四个参数BiTNode parent是BiTNode型,
因此传第四个参数时不能直接传T,因为T是BiTree型,由于此时是根结点,父指针必为NULL,所以
可以定义一个BiTNode类型的变量,让其父指针为NULL*/
BiTNode parentT;
parentT.parent = NULL;
InsertFileData(list,T->nameFileNode,T,parentT);
}
/*---------------------------------------吴佳峻---------------------------------------*/
int main()
{
//1.创建文件夹顺序表->可以是全局变量,也可以是局部变量
FileList list; /*注:全局变量必须有初值*/
//2.初始化文件夹顺序表->函数调用必须在main函数里
initFileList(&list,1); /*初始化容量为1*/
/*
list->data:指向一块可以容纳1个FileNode的连续内存空间
list->numFile = 0:当前实际存储的文件夹个数,初始时为0
list->capacity = 1:数组的最大容量,表示当前最多可以存储1个FileNode
*/
//3.创建文件夹二叉树->初始为一个空树
BiTree root=NULL;
//4.初始化文件夹二叉树
initBiTree(root,list);
//界面
printf("------------------------------------------- \n");
printf(" 1.创建文件夹 2.删除文件夹 \n");
printf(" 3.查找文件夹 4.退出 \n");
printf("------------------------------------------- \n");
while(true)
{
printf("请输入接下来的操作编号:");
int choose;
scanf("%d",&choose);
printf("------------------------------------------- \n");
switch(choose)
{
case 1:
{
//输入信息
printf("请输入新创建的文件夹名称:");
char inputFile[50];
scanf("%s",inputFile);
creatFile(root,inputFile,list);
printf("------------------------------------------- \n");
break;
}
case 2:
{
// 修改这里的调用
deleteFileInteractive(root, list);
printf("------------------------------------------- \n");
break;
}
case 3:
{
//输入信息
printf("请输入要查找的文件夹:");
char findFileName[50];
scanf("%s",findFileName);
findFile(list,findFileName);
printf("------------------------------------------- \n");
break;
}
case 4:
{
exit(0);
}
default:
{
printf("该操作不存在 \n");
printf("------------------------------------------- \n");
break;
}
}
}
return 0;
}