这个作业属于哪个班级 | 数据结构--网络2011/2012 |
---|---|
这个作业的地址 | DS博客作业03--树 |
这个作业的目标 | 学习树结构设计及运算操作 |
姓名 | 郑俊佳 |
0.PTA得分截图
1.本周学习总结(5分)
1.1 二叉树结构
1.1.1 二叉树的2种存储结构
顺序存储结构:
把一个满二叉树自上而下、从左到右顺序编号,依次存放在数组内。设满二叉树结点在数组中的索引号为i,那么有如下性质。
(1)如果i = 0,此结点为根结点,无双亲。
(2)如果i > 0,则其双亲结点为(i -1) / 2 。(注意,这里的除法是整除,结果中的小数部分会被舍弃。)
(3)结点i的左孩子为2i + 1,右孩子为2i + 2。
(4)如果i > 0,当i为奇数时,它是双亲结点的左孩子,它的兄弟为i + 1;当i为偶数时,它是双新结点的右孩子,它的兄弟结点为i – 1。
(5)深度为k的满二叉树需要长度为2 k-1的数组进行存储。
通过以上性质可知,使用数组存放满二叉树的各结点非常方便,可以根据一个结点的索引号很容易地推算出它的双亲、孩子、兄弟等结点的编号,从而对这些结点进行访问,这是一种存储二叉满二叉树或完全二叉树的最简单、最省空间的做法。
为了用结点在数组中的位置反映出结点之间的逻辑关系,存储一般二叉树时,只需要将数组中空结点所对应的位置设为空即可,其效果如图6.8(b)所示。这会造成一定的空间浪费,但如果空结点的数量不是很多,这些浪费可以忽略。
一个深度为k的二叉树需要2 k-1个存储空间,当k值很大并且二叉树的空结点很多时,最坏的情况是每层只有一个结点,再使用顺序存储结构来存储显然会造成极大地浪费,这时就应该使用链式存储结构来存储二叉树中的数据。
#define Maxsize 100
typedef struct TNode {
char tree[Maxsize]; //数组存放二叉树中的节点
int parent; //表示双亲结点的下标
}TNode, * BTree;
链式存储结构:
二叉树的链式存储结构可分为二叉链表和三叉链表。二叉链表中,每个结点除了存储本身的数据外,还应该设置两个指针域left和right,它们分别指向左孩子和右孩子(如图6.9(a)所示)。
当需要在二叉树中经常寻找某结点的双亲,每个结点还可以加一个指向双亲的指针域parent,这就是三叉链表。
二叉树还有一种叫双亲链表的存储结构,它只存储结点的双亲信息而不存储孩子信息,由于二叉树是一种有序树,一个结点的两个孩子有左右之分,因此结点中除了存放双新信息外,还必须指明这个结点是左孩子还是右孩子。由于结点不存放孩子信息,无法通过头指针出发遍历所有结点,因此需要借助数组来存放结点信息。图6.10(a)所示的二叉树使用双亲链表进行存储将得到图6.11所示的结果。由于根节点没有双新,所以它的parent指针的值设为-1。
双亲链表中元素存放的顺序是根据结点的添加顺序来决定的,也就是说把各个元素的存放位置进行调换不会影响结点的逻辑结构。由图6.11可知,双亲链表在物理上是一种顺序存储结构。
二叉树存在多种存储结构,选用何种方法进行存储主要依赖于对二叉树进行什么操作来确定。而二叉链表是二叉树最常用的存储结构,下面几节给出的有关二叉树的算法大多基于二叉链表存储结构。
typedef struct TNode { //二叉树结点由数据域,左右指针组成
char data;
struct TNode* lchild;
struct TNode* rchild;
}TNode, * BTree;
1.1.2 二叉树的构造
二叉树一般是将顺序存储的转化为链式
结构体如下:
typedef struct node{
ElemType data;
struct node *lchild;
struct node *rchild;
}BTNode,*BTree;
1. 顺序存储结构转二叉链
根据双亲与孩子的关系---->双亲2是左孩子下标,双亲2+1是右孩子下标,
进行递归调用,建立二叉树。
/*函数设计*/
BTree CreatTree(string str, int& i)
{
int len = str.size();
BTree bt;
/*递归出口*/
if (i > len - 1||i<0) return NULL;
if (str[i] == '#')return NULL;
bt = new BTNode;
bt->data = str[i];
bt->lchild = CreatTree(str, 2*i);//左右递归,建立左右孩子
bt->rchild = CreatTree(str, 2*i+1);
return bt;
}
2. 先序建树---根左右
建树时按照先建立根,再建立左子树,最后右子树的方式进行建树
所以采用递归的方式建树
递归出口---字符串结束,或者碰到#
BTree CreatTree(string str, int& i)//先序遍历建树
{
int len = str.size();
BTree bt;
/*递归出口*/
if (i > len - 1) return NULL;
if (str[i] == '#')return NULL;
bt = new BTNode;
bt->data = str[i];
bt->lchild = CreatTree(str, ++i);//左右递归
bt->rchild = CreatTree(str, ++i);
return bt;
}
3. 层次建树--队列
一层一层的建立,则需要储存每层数据,就需要队列与之搭配完成建树
void creatbintree(BTree& bt, string s)
{
int i = 1;
BTree p;
bt = new BTNode;
if (s[i] == '#') {//如果第一个节点为空,就直接返回空树
bt = NULL;
return;
}
else {//创建根节点
bt->data = s[i];
bt->lchild = bt->rchild = NULL;
q.push(bt); //根节点入队
}
while (!q.empty()) { //当队列不为空
p = q.front();
q.pop();
i++;
p->lchild =new BTNode;//创建左孩子
if (s[i] == '#') p->lchild = NULL; //左孩子为空
else {
p->lchild->data = s[i];
p->lchild->lchild = p->lchild->rchild = NULL;
q.push(p->lchild); //左孩子入队
}
p->rchild = new BTNode;//创建右孩子
i++;
if (s[i] == '#') p->rchild = NULL; //右孩子为空
else {
p->rchild->data = s[i];
p->rchild->lchild = p->rchild->rchild = NULL;
q.push(p->rchild); //右孩子入队
}
}
}
4. 括号法建树--栈
例如:A(B(D(,G)),C(E,F))
-
- 单个字符:结点的值
-
- (:表示一棵子树的开始
-
- ):表示一棵子树的结束
-
- ,:表示一棵右子树的开始
void CreateTree(BTree& b, char str[])
{
char ch;
BTree stack[MaxSize], p;//stack[MaxSize]为指针数组,其每一个元素都为指向bitnode这种结构的指针,p为临时指针
int top = -1, k, j = 0; //top为栈顶指针、k决定谁是左、右孩子、j为str指针
while ((ch = str[j++]) != '\0'){
switch (ch){
case '(':
top++;
stack[top] = p;//根节点入栈
k = 1; //1为左孩子
break;
case ',':
k = 2; //2为右孩子
break;
case ')':
top--; //根节点出栈
break;
default:
p = new BTNode;
p->data = ch;
p->lchild = p->rchild = NULL;
if (b == NULL) b = p; //树为空时
else{//树非空时
switch (k){
case 1:
stack[top]->left = p; //根节点的左孩子
break;
case 2:
stack[top]->right = p; //根节点的右孩子
break;
}
}
}
}
}
此外,还可以根据给出先序和中序,或中序和后序得到二叉树
先序+中序
-
- 根据先序序列可得,根节点在开头,
-
- 根据中序序列可得,根节点在中间,
-
- 于是通过中序序列得到左右两支树
BTree CreatTree(int n, char* pre, char* mid)
{
if (n <= 0)return NULL;
BTree T;
char* p;
T = new BTNode;
T->data = *pre;//先序的第一个一定是根节点
T->lchild = NULL;
T->rchild = NULL;
for (p = mid; p < mid+n; p++)//中序找根节点,将左右子树分开
if (*p==*pre)break;
int i = p - mid;
T->lchild = CreatTree(i, pre + 1, mid);
T->rchild = CreatTree(n - 1 - i, pre + i + 1, mid + 1 + i);
return T;
}
后序+中序
-
- 根据后序序列可得,根节点在结尾,
-
- 根据中序序列可得,根节点在中间,
-
- 于是通过中序序列得到左右两支树
BTree CreatTree(int n, int* last, int* mid)
{
if (n <= 0)return NULL;
BTree T;
T = new BTNode;
T->data = last[n - 1];//后序的最后一个一定是根节点
T->lchild = NULL;
T->rchild = NULL;
int i;
for (i = 0; i < n; i++)//中序找根节点,将左右子树分开
if (mid[i] == last[n - 1])break;
T->lchild = CreatTree(i, last, mid);
T->rchild = CreatTree(n - 1 - i, last + i, mid + 1 + i);
return T;
}
注意:没有先序+后序建树,两者虽然都可以确定根的位置,但没办法将左右子树分开,无法唯一确定二叉树
1.1.3 二叉树的遍历
先序遍历(根左右)
-
- 访问根节点;
-
- 先序遍历左子树;
-
- 先序遍历右子树;
-
- 先序遍历的递归过程为:若二叉树为空,遍历结束。否则:①访问根结点;②先序遍历根结点的左子树;③先序遍历根结点的右子树。 简单来说先序遍历就是在深入时遇到结点就访问。
先序遍历的递归算法:
- 先序遍历的递归过程为:若二叉树为空,遍历结束。否则:①访问根结点;②先序遍历根结点的左子树;③先序遍历根结点的右子树。 简单来说先序遍历就是在深入时遇到结点就访问。
void PreOrder(BTree bt)
{ if (bt!=NULL)
{ printf("%c ",bt->data); //访问根结点
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
先序遍历非递归算法:
若二叉树bt不空,则入栈根节点bt。
while(栈不空)
{ 出栈栈顶,访问根节点。
if(bt有右孩子) 入栈bt->rchild。
if(bt有左孩子) 入栈bt->lchild。
}
中序遍历(左根右)
-
- 中序遍历左子树;
-
- 访问根节点;
-
- 中序遍历右子树;
-
- 中序遍历的递归过程为:若二叉树为空,遍历结束。否则:①中序遍历根结点的左子树;②访问根结点;③中序遍历根结点的右子树。简单来说中序遍历就是从左子树返回时遇到结点就访问。
中序遍历的递归算法:
- 中序遍历的递归过程为:若二叉树为空,遍历结束。否则:①中序遍历根结点的左子树;②访问根结点;③中序遍历根结点的右子树。简单来说中序遍历就是从左子树返回时遇到结点就访问。
void InOrder(BTree bt)
{
if (bt!=NULL)
{
InOrder(bt->lchild);
printf("%c ",bt->data); //访问根结点
InOrder(bt->rchild);
}
}
后序遍历(左右根)
-
- 后序遍历左子树;
-
- 后序遍历右子树;
-
- 访问根节点;
-
- 后序遍历的递归过程为:若二叉树为空,遍历结束。否则:①后序遍历根结点的左子树;②后序遍历根结点的右子树;③访问根结点。简单来说后序遍历就是从右子树返回时遇到结点就访问。
后序遍历的递归算法:
- 后序遍历的递归过程为:若二叉树为空,遍历结束。否则:①后序遍历根结点的左子树;②后序遍历根结点的右子树;③访问根结点。简单来说后序遍历就是从右子树返回时遇到结点就访问。
void PostOrder(BTree bt)
{
if (bt!=NULL)
{
PostOrder(bt->lchild);
PostOrder(bt->rchild);
printf("%c ",bt->data); //访问根结点
}
}
层次遍历
这棵二叉树的层次遍历次序为:A、B、C、D、F、G 每次出队一个元素,就将该元素的孩子节点加入队列中,直至队列中元素个数为0时,出队的顺序就是该二叉树的层次遍历结果.
void PrintTree(BTree BT)//层次遍历二叉树
{
BTree ptr;//遍历二叉树
queue<BTree>qu;
qu.push(BT);//根结点进栈
while (!qu.empty())//若队列不为空
{
ptr = qu.front();//第一个元素出栈
qu.pop();
cout << ptr->data;
if (ptr->lchild != NULL)//若出栈元素有左右子结点,进栈
qu.push(ptr->lchild);
if (ptr->rchild != NULL)
qu.push(ptr->rchild);
}
}
1.1.4 线索二叉树
在二叉树的结点上加上线索的二叉树称为线索二叉树。每个节点有两个指针域,n个结点总共有2n个指针域,非空链域为n-1个,空链域有n+1个
结构体定义:
typedef struct node
{ ElemType data; //结点数据域
int ltag,rtag; //增加的线索标记
struct node *lchild; //左孩子或线索指针
struct node *rchild; //右孩子或线索指针
} TBTNode; //线索树结点类型定义
线索二叉树性质:
-
- 1)若结点有左子树,则lchild指向其左孩子;否则, lchild指向其直接前驱(即线索);
-
- 2)若结点有右子树,则rchild指向其右孩子;否则, rchild指向其直接后继(即线索) 。
为了表示有无左右孩子,增加两个标志域:
- 2)若结点有右子树,则rchild指向其右孩子;否则, rchild指向其直接后继(即线索) 。
-
- LTag :若 LTag=0, lchild域指向左孩子; 若 LTag=1, lchild域指向其前驱。
-
- RTag :若 RTag=0, rchild域指向右孩子; 若 RTag=1, rchild域指向其后继。
中序线索二叉树
中序线索二叉树可以找到对应树每个节点的前驱和后继节点。先序和后序线索二叉树无法做到。
优点:中序遍历算法既没有递归也没有用栈,所有节点只需遍历一次,空间效率得到提高。
-
- 结点的后继:(前继同理)
-
- 结点有右孩子,则为右子树最左孩子节点
-
- 结点无右孩子,则为后继线索指针指向节点
TBTNode* pre; //全局变量
TBTNode* CreatThread(TBTNode* b) //中序线索化二叉树
{
TBTNode* root;
root = (TBTNode*)malloc(sizeof(TBTNode)); //创建头结点
root->ltag = 0; root->rtag = 1; root->rchild = b;
if (b == NULL) root->lchild = root; //空二叉树
else
{
root->lchild = b;
pre = root; //pre是*p的前驱结点,供加线索用
Thread(b); //中序遍历线索化二叉树
pre->rchild = root; //最后处理,加入指向头结点的线索
pre->rtag = 1;
root->rchild = pre; //头结点右线索化
}
return root;
}
void Thread(TBTNode*& p) //对二叉树b进行中序线索化
{
if (p != NULL)
{
Thread(p->lchild); //左子树线索化
if (p->lchild == NULL) //前驱线索化
{
p->lchild = pre; p->ltag = 1;
} //建立当前结点的前驱线索
else p->ltag = 0;
if (pre->rchild == NULL) //后继线索化
{
pre->rchild = p; pre->rtag = 1;
} //建立前驱结点的后继线索
else pre->rtag = 0;
pre = p;
Thread(p->rchild); //递归调用右子树线索化
}
}
1.1.5 二叉树的应用--表达式树
如图所示为表达式3x2+x-1/x+5的二叉树表示。树中的每个叶结点都是操作数,非叶结点都是运算符。
对该二叉树分别进行先序、中序和后序遍历,可以得到表达式的三种不同表示形式。
-
- 前缀表达式+-+3xxx/1x5
-
- 中缀表达式3xx+x-1/x+5
-
- 后缀表达式3xx**x+1x/-5+
表达式树的构建和输出:
- 后缀表达式3xx**x+1x/-5+
#include<stdio.h>
#include<string.h>
typedef struct binode
{
char data[4];
int h;
int depth;
struct binode *lchild,*rchild;
}binode,*bitree;
char d[100][100];
int q=0,num1;
void creatbitree(bitree &T,int y,int num)
{
if(d[q][0]=='#') {T=NULL;q++;}
else
{
T=new binode;
if(y==1) T->h=1;
else T->h=0;
T->depth=++num;
strcpy(T->data,d[q++]);
creatbitree(T->lchild,1,T->depth);
creatbitree(T->rchild,0,T->depth);
}
}
void travel(bitree T)
{
int i;
if(T!=NULL)
{
if(T->data[0]=='+'||T->data[0]=='-'||T->data[0]=='*'||T->data[0]=='/')
{
printf("(");
travel(T->lchild);
printf("%s",T->data);
travel(T->rchild);
printf(")");
}
else
printf("%s",T->data);
}
}
int ldepth(bitree T)
{
if(T==NULL)
return 0;
num1=ldepth(T->lchild);
return num1+1;
}
int rdepth(bitree T)
{
if(T==NULL)
return 0;
num1=rdepth(T->rchild);
return num1+1;
}
int main()
{
char a[500];
bitree T;
while(gets(a)!=NULL)
{
int i,j=0,k=0;
q=0;
for(i=0;a[i]!='\0';i++)
{
if(a[i]!=' ')
d[j][k++]=a[i];
else
{
d[j][k++]='\0';
//puts(d[j]);
k=0;
j++;
}
}
d[j++][k++]='\0';
//printf("%d\n",j);
creatbitree(T,2,0);
travel(T);
printf("\n");
}
}
1.2 多叉树结构
定义:它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
1.2.1 多叉树结构
双亲存储结构
结构体定义:
typedef struct
{
ElemType data; //结点的值
int parent; //指向双亲的位置
}PTree[MaxSize];
缺点:找父亲容易,找孩子不容易
孩子链存储结构
结构体定义:
typedef struct node
{
ElemType data; //结点的值
struct tnode *sons[MaxSons]; //指向孩子结点
}TSonNode;
缺点:空指针太多,找父亲不容易
孩子兄弟链存储结构
孩子兄弟链存储结构是为每个结点设计3个域:
-
- 一个数据元素域
-
- 第一个孩子结点指针域
-
- 一个兄弟结点指针域
结构体定义:
- 一个兄弟结点指针域
typedef struct tnode
{
ElemType data; //结点的值
struct tnode *son; //指向兄弟
struct tnode *brother; //指向孩子结点
}TSBNode;
1.2.2 多叉树遍历
给定一个 N 叉树,返回其节点值的前序遍历。
返回其前序遍历: [1,3,5,6,2,4]。
class Solution {
public List<Integer> res = new ArrayList<Integer>();
public List<Integer> preorder(Node root) {
if(root == null)
return res;
res.add(root.val);
for(Node child : root.children){
preorder(child);
}
return res;
}
1.3 哈夫曼树
1.3.1 哈夫曼树定义
哈夫曼树:给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
解决问题:
哈夫曼静态编码,哈夫曼动态编码
在数据通信中,需要将传送的文字转换成二进制的字符串,用0,1码的不同排列来表示字符。例如,需传送的报文为“AFTER DATA EAR ARE ART AREA”,这里用到的字符集为“A,E,R,T,F,D”,各字母出现的次数为{8,4,5,3,1,1}。现要求为这些字母设计编码。
1.3.2 哈夫曼树的结构体
顺序结构:
typedef struct
{ char data; //节点值
float weight; //权重
int parent; //双亲节点
int lchild; //左孩子节点
int rchild; //右孩子节点
} HTNode;
初始化哈夫曼树
typedef struct
{
int data;
int parent;
int lchild;
int rchild;
}HTNode,*HuffmanTree;
void CreateHTree(HuffmanTree &ht, int n)
{
int len;
len = 2 * n - 1;
ht = new HTNode[len];
}
1.3.2 哈夫曼树构建及哈夫曼编码
哈夫曼树构建:
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
-
- (1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
-
- (2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
-
- (3)从森林中删除选取的两棵树,并将新树加入森林;
-
- (4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
- (4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
哈夫曼编码:
利用哈夫曼树求得的二进制编码称为哈夫曼编码。树中从根到每个叶子节点都有一条路径,对路径上的各分支约定指向左子树的分支表示”0”码,指向右子树的分支表示“1”码,取每条路径上的“0”或“1”的序列作为各个叶子节点对应的字符编码,即是哈夫曼编码。
A,B,C,D对应的哈夫曼编码分别为:111,10,110,0
1.4 并查集
并查集,在一些有N个元素的集合)应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
并查集解决问题
初始化:每个点看做一棵树 ,并且为每个树的树根;树根就是每个组别的代表。
查询:对于点对(a,b),通过a和b去向上查找他们的祖先节点直到树根,如果有相同的祖先节点,则他们在已经在一棵树下,属于同一组别。
合并:若不在同一组别,令其中一个点(比如a)所在树的根节点成为另一个点(比如b)的根节点的孩子。这样即便再查询到a,最终会判断认为a属于b的组别。
大树小树合并技巧: 小树变成大树的子树,会比大树变成小树的子树更加不易增加树高,这样可以减少查询次数。
并查集的结构体
typedef struct node
{ int data; //结点对应人的编号
int rank; //结点秩:子树的高度,合并用
int parent; //结点对应双亲下标
} UFSTree; //并查集树的结点类型
初始化并查集
void MAKE_SET(UFSTree t[],int n) //初始化并查集树
{ int i;
for (i=1;i<=n;i++)
{ t[i].data=i; //数据为该人的编号
t[i].rank=0; //秩初始化为0
t[i].parent=i; //双亲初始化指向自已
}
}
并查集的查找
int FIND_SET(UFSTree t[],int x) //在x所在子树中查找集合编号
{ if (x!=t[x].parent) //双亲不是自已
return(FIND_SET(t,t[x].parent)); //递归在双亲中找x
else
return(x); //双亲是自已,返回x
}
并查集的合并
void UNION(UFSTree t[],int x,int y) //将x和y所在的子树合并
{ x=FIND_SET(t,x); //查找x所在分离集合树的编号
y=FIND_SET(t,y); //查找y所在分离集合树的编号
if (t[x].rank>t[y].rank) //y结点的秩小于x结点的秩
t[y].parent=x; //将y连到x结点上,x作为y的双亲结点
else //y结点的秩大于等于x结点的秩
{ t[x].parent=y; //将x连到y结点上,y作为x的双亲结点
if (t[x].rank==t[y].rank) //x和y结点的秩相同
t[y].rank++; //y结点的秩增1
}
}
1.5.谈谈你对树的认识及学习体会。
-
- 树是非线性结构,但也需要线性结构进行辅助完成,例如层次遍历需要队列,表达式二叉树需要栈进行辅助,对整体把握性要求较高
-
- 尽量自己多动手画图,才能真正理解,懂得怎么遍历实现的
-
- 学习一些算法优化的方法,需要多了解一些STL库中的东西,sort,堆排等等
2.PTA实验作业(4分)
2.1 二叉树
输出二叉树每层节点、二叉表达式树、二叉树叶子结点带权路径长度和 三题自选一题介绍。
2.1.1 解题思路及伪代码
解题思路
建立二叉树
层次输出需要借助队列暂时储存每层元素,要有一个该层结束的标志---b==eb
还需要有每层的高度
void LevelOrder(BTree bt)
{
BTree b,eb;//结尾所在的树枝eb
b=eb=bt;//最开始都在根部
queue<BTree>q;//队列
if bt 空 cout<<"NULL"; return ;
bt进队列
while q不空
//每层结束
if b==eb//当前的树是该层最后一个时
cout<<h++;//可以用flag 控制换行与不换行
eb=q.back();//更新eb
b=q.front();//从最左侧开始
q.pop();
cout<< b->data << ",";
if b左孩子存在 左孩子进队列;
if b右孩子存在 右孩子进队列;
end while
}
2.1.2 总结解题所用的知识点
-
- 前面的线性结构队列,也可以在非线性结构利用,毕竟非线性结构也是由很多线性结构拼接的
-
- 二叉树的层次遍历要牢记
-
- 利用队列将该层的头与尾结合,控制输出每层
2.2 目录树
输入格式:
输入首先给出正整数N(≤104),表示ZIP归档文件中的文件和目录的数量。随后N行,每行有如下格式的文件或目录的相对路径和名称(每行不超过260个字符):
路径和名称中的字符仅包括英文字母(区分大小写);
符号“\”仅作为路径分隔符出现;
目录以符号“\”结束;
不存在重复的输入项目;
整个输入大小不超过2MB。
输出格式:
假设所有的路径都相对于root目录。从root目录开始,在输出时每个目录首先输出自己的名字,然后以字典序输出所有子目录,然后以字典序输出所有文件。注意,在输出时,应根据目录的相对关系使用空格进行缩进,每级目录或文件比上一级多缩进2个空格。
输入样例:
7
b
c\
ab\cd
a\bc
ab\d
a\d\a
a\d\z\
输出样例:
root
a
d
z
a
bc
ab
cd
d
c
b
2.2.1 解题思路及伪代码
解题思路
分析题目:
本题主要分为两个子问题:一是根据输入的信息建立树,二是根据树的结构输出文件目录
依题意,文件树需要用左孩子右兄弟的二叉链表存储
root是根目录,所以首先建立根节点。在扫描每一行字符串的时候,都从root开始,逐一向下将每层结点插入相应的兄弟链表中
结点是先序遍历输出
实现要点:
建树,需要注意输出的顺序,即同层目录排在文件前,同类按字典顺序输出
输出时,注意不同层结点输出不同的缩进
设计目录树,结构体 → 初始化树,新建根节点 → 建树:扫面字符串,分离文件、目录 → 插入目录树 → 输出树
建树的核心思路:
目录的插入优先级高于文件,即目录相当于非叶结点,文件相当于叶结点。所以,文件不管是否按照自带你顺序排列,和目录比它都要往后移
每次只处理一行字符串,都是从根节点root开始逐一插入这行的目录或文件
插入优先级相同的字典序在前
void CreatTree(Tree&bt ,string str,int i)
{
定义结构体指针temp,btr;
为temp申请空间并初始化,btr用于指向bt;
if(i>=str.size())
return;//路径遍历完毕
获取结点temp的名字
if(str[i]=='\\')
说明结点temp为目录,修改temp->isfile为true;
end if
if(temp为文件)
InitFile(temp,bt);//为文件temp在bt的孩子中找一个可插入位置
else //temp为目录
InitList(temp,bt);//为目录temp在bt的孩子中找一个可插入位置
CreatTree(temp,str,i);
}
void InitList(Tree& temp, Tree& bt)//对目录temp找一个插入位置
{
定义结构体指针btr来遍历二叉树bt
btr=bt->child;//btr先指向bt的孩子;
//对第一个兄弟结点进行判断
if(btr==NULL||btr为文件||temp->name<btr->name)//可插入
进行插入,要注意修改bt的孩子指针;
else if(temp->name == btr->name)
直接使temp指向btr;
else //开始从第二个兄弟结点查找插入位置
while(btr->brother != NULL)
if(btr->brother为文件||btr->brother->name>temp->name)
找到可插入位置,break;
else if(btr->brother->name == temp->name)
直接使temp指向btr->brother;break;
else
btr=btr->brother;//遍历下一兄弟结点
end if
end while
if(btr->brother为空||btr->brother->name!= temp->name)
进行插入操作:temp->brother=btr->brother;btr->brother=temp;
end if
end if
}
void InitFile(Tree& temp, Tree& bt)//对文件temp找一个可插入位置
{
定义结构体指针btr来遍历二叉树bt;
btr=bt->child;//btr先指向bt的孩子;
if(btr==NULL||btr为文件&&btr->name>=temp->name)//对第一个兄弟结点进行判断
进行插入,注意修改bt的孩子指针
else //从第二个兄弟结点进行判断
while(btr->brother != NULL)
if (btr->brother为文件&&btr->brother->name>temp->name)
找到可插入位置,break;
else
btr = btr-> brother;//遍历下一个兄弟结点
end if
end while
对temp进行插入操作:temp->brother=btr->brother;btr->brother=temp;
end if
}
2.2.2 总结解题所用的知识点
-
- 结点插入树,分为孩子和兄弟,采用孩子兄弟链
-
- 首先建立目录树。将输入的每个字符串插入到已有的目录树中,插入时将字符串的前缀与目录树的结点一层一层往下匹配,失配时创建新的目录和文件
-
- 后序遍历目录树,对每个结点的子目录和子文件进行排序
-
- 先序遍历进行输出。
3.阅读代码(0--1分)
找1份优秀代码,理解代码功能,并讲出你所选代码优点及可以学习地方。主要找以下类型代码:
考研题
蓝桥杯题解,这个连接只是参考的题目,具体可以自己搜索蓝桥杯,查看历年的题解。只能找树相关题目介绍。
leecode--树
注意:不能选教师布置在PTA的题目。完成内容如下。
3.1 题目及解题代码
可截图,或复制代码,需要用代码符号渲染。
3.2 该题的设计思路及伪代码
请用图形方式展示解决方法。同时分析该题的算法时间复杂度和空间复杂度。