这个作业属于哪个班级 数据结构--网络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指向其直接后继(即线索) 。
      为了表示有无左右孩子,增加两个标志域:
    • 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+
      表达式树的构建和输出:
#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)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

哈夫曼编码:
利用哈夫曼树求得的二进制编码称为哈夫曼编码。树中从根到每个叶子节点都有一条路径,对路径上的各分支约定指向左子树的分支表示”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 该题的设计思路及伪代码

请用图形方式展示解决方法。同时分析该题的算法时间复杂度和空间复杂度。

3.3 分析该题目解题优势及难点。

posted on 2021-05-02 14:57  jioky  阅读(39)  评论(0编辑  收藏  举报