zoukankan      html  css  js  c++  java
  • DS--树

    这个作业属于哪个班级 数据结构--网络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++]) != ''){
            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]!='';i++)
            {
                if(a[i]!=' ')
                        d[j][k++]=a[i];
                else
                    {
                        d[j][k++]='';
                        //puts(d[j]);
                        k=0;
                        j++;
                    }
            }
            d[j++][k++]='';
            //printf("%d
    ",j);
            creatbitree(T,2,0);
            travel(T);
            printf("
    ");
        }
    }
    

    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
    abcd
    ac
    abd
    ada
    adz
    输出样例:
    
    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 分析该题目解题优势及难点。

  • 相关阅读:
    《WF编程》系列之32 基本活动:条件与规则 4.5 条件与规则
    《WF编程》系列之31 基本活动:事务(Transactions)与补偿(Compensation) 4.4 事务(Transactions)与补偿(Compensation)
    《WF编程》系列之30 基本活动:错误处理
    《WF编程》系列之33 基本活动:Web Services 4.6 Web Services
    《WF编程》系列之36 自定义活动:如何创建自定义活动?活动的组合 5.2 如何创建自定义活动?
    《WF编程》系列之35 自定义活动:为何创建自定义活动? 5 自定义活动
    《WF编程》系列之29 本地通信事件:HandleExternalEventActivity & 活动生成器 4.2.2 HandleExternalEventActivity
    《WF编程》系列之34 基本活动:状态活动 到目前为止,我们所讨论的工作流都是顺序工作流,而WF还支持另外一种工作流机制状态机(StateMachine)工作流,本节就来介绍这些在状态机工作流中工作的活动.
    《WF编程》系列之37 打开黑盒子:属性升级.
    《WF编程》系列之28 本地通信事件:CallExternalMethodActivity
  • 原文地址:https://www.cnblogs.com/jioky/p/14725724.html
Copyright © 2011-2022 走看看