DS博客作业03--树

|这个作业属于哪个班级|数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业03--树 |
| 这个作业的目标 |学习树结构设计及运算操作 |
| 姓名 | 陈佳桐 |

0.PTA得分截图

1.本周学习总结(5分)

学习总结,请结合树的图形展开分析。

1.0树

树是由n个节点组成的有限集合(计为T)。

如果n=0,它是一棵空树。
如果n>0,这n个节点中有且仅有一个节点作为树的根节点,简称为根。

1.1 二叉树结构

1.1.1 二叉树的2种存储结构

树的顺序存储和链式存储结构,并分析优缺点。

树的顺序存储结构

定义:用一组地址连续的存储单元来存放二叉树的数据元素。

对于完全二叉树和满二叉树,可以用一维数组按从上到下,从左往右的顺序存储树中的所有节点值,通过元素下标关系反映树中节点的逻辑关系
对于一般二叉树,需要添加一些并不存在的空结点,使之成为一颗完全二叉树形式。

优点:可以利用数组元素的下标确定结点在二叉树中的位置以及结点之间的关系。对于完全二叉树或满二叉树使用可节省存储空间,存储空间利用率高。

缺点:对于不接近完全二叉树的一般二叉树需要分配多个空结点,进行插入、删除等运算时很不方便。

树的链式存储结构

定义:用一个链表来存储一颗二叉树,二叉树中的每一个结点用链表中的一个结点来存储。

优点:对于一般的二叉树比较节省存储空间,在二叉链中访问一个结点的孩子很方便。

缺点:访问一个结点的双亲结点需要扫描所有结点。

二叉树基本运算

创建二叉树CreateBTree(b,str):根据二叉树括号表示法字符串str生成对应的二叉链存储结构,b为创建的二叉链的根结点指针。
销毁二叉树DestroyBTree(&b):释放二叉树b中所有结点分配的空间。
查找结点
找孩子节点

二叉树的创建

先序创建

BTree CreateBT(string str,int&i)
{
    if(i>=len-1)
        return NULL;
    if(str[i]=='#')
        return NULL;
        
     BTree bt=new BTnode;
     bt->data=str[i];
     bt->lchild=CreateBT(str,++i);
     bt->rchild=CreateBT(str,++i);
}

中序创建

BTree CreateBT(string str,int&i)
{
    if(i>=len-1)
        return NULL;
    if(str[i]=='#')
        return NULL;       
     BTree bt=new BTnode;   
     bt->lchild=CreateBT(str,++i);
     bt->data=str[i];
     bt->rchild=CreateBT(str,++i);
}

后序创建

BTree CreateBT(string str,int&i)
{
    if(i>=len-1)
        return NULL;
    if(str[i]=='#')
        return NULL;       
     BTree bt=new BTnode;   
     bt->lchild=CreateBT(str,++i);     
     bt->rchild=CreateBT(str,++i);
     bt->data=str[i];
}

1.1.2 二叉树的构造

总结二叉树的几种构造方法。分析你对这些构造方法的看法。务必介绍如何通过先序遍历序列和中序遍历序列、后序遍历序列和中序遍历序列构造二叉树。

通过先序遍历序列和中序遍历序列构造二叉树

定理一:任何n(n>=0)个不同结点的二叉树,都可由它的中序序列和先序序列唯一确定。

 BTree Create(char *pre, char *in,int n)
{
 BTNode*s;char *p; int k;
 if(n<=0) return NULL;
 s= new BTNode;
 s->data= *pre ;
 for (p=in; p<in+n;p++)
 if(*p==*pre)
 break;
 k=p-in;
 s->ichild =CreateBT1(pre+1,in,k); //构造左子树
 s->rchild =CreateBT1(pre+k+1,p+1,n-k-1);//右子树
return s;
}

通过后序遍历序列和中序遍历序列构造二叉树

定理二:任何n(n>=0)个不同结点的二叉树,都可由它的中序序列和后序序列唯一确定。

BTRee CreateBT2(char * post,char *in,int n)
{
BTNode *s; char *p; int k;
if(n<=0) return NULL;
 s= new BTNode;//创建节点
 s->data=*(post+n-1);//构造根节点
 for (p=in; p<in+n;p++)//在中序中找为*ppos的位置k
 if(*p==*(post+n-1))
 break;
 k=p-in;
 s->ichild =CreateBT2(post,in,k); //构造左子树
 s->rchild =CreateBT2(post+k,p+1,n-k-1);//构造右子树
 return s;
}

1.1.3 二叉树的遍历

总结二叉树的4种遍历方式,如何实现。

先序遍历

void PreOrder(BTree bt)
{
	if (bt != NULL)
	{
		printf("%c", bt->data);//访问根节点
		PreOrder(bt->lchild);//先序遍历左子树
		PreOrder(bt->rchild);//先序遍历右子树
	}
}

中序遍历

void PreorderPrintLeaves(BinTree BT)
{
    if (BT != NULL)
    {
 
       PreorderPrintLeaves(BT->Left);
            cout<<BT->Data<<" ";    
        PreorderPrintLeaves(BT->Right);

    }
}

后序遍历

void PreorderPrintLeaves(BinTree BT)
{
    if (BT != NULL)
    {   
        PreorderPrintLeaves(BT->Left);
        PreorderPrintLeaves(BT->Right);
            cout<<BT->Data<<" ";
    }
}

层次遍历

void LevelorderTraversal(BinTree BT)
{
	if (BT == NULL)
		return;
	queue<BinTree>Que;
	BinTree front;
	Que.push(BT);
	while (!Que.empty())
	{
		front = Que.front();
		Que.pop();
		printf("%c", front->data);
		if (front->lchild != NULL)
			Que.push(front->lchild);
		if (front->rchild != NULL)
			Que.push(front->rchild);
	}
}

1.1.4 线索二叉树

线索二叉树设计
利用空链域,指向该线性序列中的“前驱”和“后继”的指针,称作线索。

1)若结点有左子树,则lchild指向其左孩子;否则,lchild指向其直接前驱(即线索);
2)若结点有右子树,则rchild指向其右孩子;否则,rchild指向其直接后继(即线索);

代码如下

typedf struct node 
{
  ElemType data ;        //结点数据域
  int ltag ,rtag;        //增加的线索印记
  struct node *lchild ;  //左孩子或线索指针
  struct node *rchild;   //右孩子或线索指针
} TBTNode;               //线索树结点类型定义

中序线索二叉树

特点:

0.在线索二叉树中再增加一个头结点
1.头结点左孩子指向根节点
2.右孩子为线索,指向最后一个孩子
3.遍历序列第一个结点前驱为头结点,最后一个节点后继为头结点

在中序线索二叉树查找前驱和后继

结点后继
结点有右孩子,则为左子树最左孩子节点
结点无右孩子,则为后继线索指针指向节点
结点前驱
结点有左孩子,则为左子树最右那个孩子
结点无左孩子,则为前驱线索指针指向结点

1.1.5 二叉树的应用--表达式树

构造细节

1.树中叶子结点均为操作数,分支结点均为运算符
2.运算符栈
3.存放树根栈

//创造表达式树

PTree CreatExpTree()
{
	char data;
	PTree T;
	Pstack P = CreatStack();
 
	cout << "请输入后缀表达式:(换行输入ctrl+z结束输入)";
	while (cin >> data)
	{
		if ('a'<= data && 'z' >= data)
		{
			T = (PTree)malloc(sizeof(Tree));
			T->data = data;
			T->left = NULL;
			T->right = NULL;
			Push(P, T);
		}
		else
		{
			T = (PTree)malloc(sizeof(Tree));
			T->data = data;
			T->right = Pop(P);
			T->left = Pop(P);
			Push(P, T);
		}
	}
	return Pop(P);    //返回树的根节点
}
//中序表达式计算
void InorderTraversal(PTree T)
{
	//递归真好用
	if (T)
	{
		if (T->left)		//如果有左子树,说明不是叶节点,应该输出一个左括号
		{
			cout << '(';
		}
		InorderTraversal(T->left);
		cout << T->data;
		InorderTraversal(T->right);
		if (T->right)		//如果有右子树,说明不是叶节点,应该输出一个右括号
		{
			cout << ')';
		}
	}
}

1.2 多叉树结构

1.2.1 多叉树结构

// 多叉树的建立、层次遍历、深度遍历
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define M 100+1 // 宏定义,定义最大名字字母长度

// 定义多叉树的节点结构体
typedef struct node_t
{
    char* name;               // 节点名
    int   n_children;         // 子节点个数
    int   level;              // 记录该节点在多叉树中的层数
    struct node_t** children; // 指向其自身的子节点,children一个数组,该数组中的元素时node_t*指针
} NODE; // 对结构体重命名

// 实现一个栈,用于后续操作
typedef struct stack_t
{
    NODE** array; // array是个数组,其元素为NODE*型指针
    int    index; // 指示栈顶元素
    int    size;  // 栈的大小
} STACK; // 重命名

// 实现一个队列,用于后续操作
typedef struct queue_t
{
    NODE** array; // array是个数组,其内部元素为NODE*型指针
    int    head;  // 队列的头
    int    tail;  // 队列的尾
    int    num;   // 队列中元素的个数
    int    size;  // 队列的大小
} QUEUE;

孩子兄弟链结构

//递归方法

typedef strcut tnode
{
  ElemType data ;     //结点的值
  struct tnode *hp ;  //指向兄弟
  struct tnode *vp ;  //指向孩子
}TSBNode;

int TreeHeight(TSBNode *t)
{
  TSBNode *p;
  int h,maxh=0;
  if(t==NULL)          //空树返回0
  return 0;
  else
  {
   p=t ->vp;
   while (p!=NULL)     //扫描t的所有子树
   {
    h=TreeHeiht2(p);   //求出p子树的高度
    if (maxh<h)        //求所有子树的最大高度
    maxh =h ;
    p=p->hp;
   }
    return(maxh+1);
}

1.2.2 多叉树遍历

介绍先序遍历做法

class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> list = new ArrayList<>();
        if (root == null){
            return list;
        }
        Stack<Node> stack = new Stack<>();
        stack.push(root);
        while (!stack.empty()){
            Node top = stack.pop();
            if (top != null){
                stack.push(top);
                stack.push(null);
                for (Node child: top.children) {
                    if (child != null){
                        stack.push(child);
                    }
                }
            }else{
                Node t = stack.pop();
                list.add(t.val);
            }
        }

        Collections.reverse(list);
        return list;
    }
}

1.3 哈夫曼树

1.3.1 哈夫曼树定义

哈夫曼树可以用于数据压缩,将复杂的待传字符成由二进制的字符串。

1.3.2 哈夫曼树的结构体

//顺序结构设计方式
typedef struct
{
  char data;
  float weight;
  int parent;
  int lchild;
  int rchild;
} HTNode;

1.3.3 哈夫曼树构建及哈夫曼编码

构造哈夫曼树的原则:
权值越大的叶结点越靠近根结点。
权值越小的叶结点越远离根结点。


1.4 并查集

定义———————查找一个元素所属的集合及合并2个元素各自专属的集合等运算。
应用———————亲戚关系、朋友圈
解决问题———集合的个数求解,集合中元素的个数求解,图的连通性问题

并查集维护的是一堆集合而不是一个集合
元素本身的值是固定不变的,但是元素所属的集合是可以变化的
并查集可以高效解决合并问题,但是合并是不可逆过程

并查集的结构体构造

tydedef 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
     {
       t[x].parent=y;
       if(t[x].rank=t[y].rank)
          t[y].rank++;
}

1.5.谈谈你对树的认识及学习体会。

个人理解:

树型结构是一类重要的非线性结构,我觉得是数据结构迈向深入学习的重要一步,了解树对于图结构的理解有重要意义。
同时,树型结构与之前学习的递归知识,顺序结构有紧密联系。
总的来说,树是承上启下的知识点。
树型结构在计算机领域中应用广泛,无论是研究算法,数据库,人工智能,都离不开树。

学习体会:

1.树的掌握,首先掌握基本术语,了解基础知识。
2.树型结构,就如字面意思,首先是以根为基础(无前驱),最远端是长出的叶子(无后继,终端结点),结点就是树的分支(数据元素),结点的度就是分支的具体数量,结点的层次就是根与分支的距离,树的高度,就是根到最远端的距离(所有结点中最大的层数)。学习树,就要理解树是怎么构成的。
3.树型结构还有一部分注重理解的,就是双亲(直接前驱)、孩子(直接后继)、兄弟(同层结点)等特殊术语,这与上一点所说相同,先理解字面与真实意思的联系,就好掌握。
4.二叉树是树结构最为重要的一部分知识,以此为基础的代码编码也涉及众多题目。
4.1二叉树有五类基本形态,子树有左子树和右子树之分。

2.PTA实验作业(4分)

2.1 二叉树

输出二叉树每层节点

题目

2.1.1 解题思路及伪代码

解题思路
1.构建 BTree CreatTree(string str ,int &i),对二叉树进行先序遍历,判断二叉树是否为空。
2.构建 void LevelOrder(BTree bt),对二叉树层次遍历,利用while进行队列遍历,flag的值控制节点“,”的生成。
3.构建 void PreOrder(BTree b,int h),对二叉树最终输出。
伪代码编写

void LevelOrder(BTree bt)
{  
   BTree p, ptr;
   将p,ptr赋予根部值
   创建队列queue<BTree>q;
   if(bt不为空)
     then 
       q.push(bt);
       bt进队
     end if

    else 
         cout << "NULL";
         return;

     int flag=0; //进行节点输出判断
   while(q不为空时)
     then
      p =q.front();  //p从最左侧开始输出
        q.pop();
      if(flag=1) 
       输出节点
      end if     
        flag=1;    
      if (p存在左孩子)  
        左孩子进队列;
      end if
     if (p存在右孩子) 
        右孩子进队列;
      end if
     
     if  (p==ptr)  //队头p位置位于该树层次最后一位节点时
      if(q不为空)
      输出当前行数
      保存节点
      end if
      i++;
      end if
   end  while
 }    
#include<iostream>
#include<string.h>
#include<queue>
using namespace std;
typedef char ElemType;
typedef  struct BiTNode
{
    ElemType data;
    struct BiTNode *lchild ,*rchild;
}BiTree,*BTree;


BTree CreatTree(string str ,int &i)
{
    BTree bt;
    int len =str.size();
    if(i>len-1 )
        return NULL;
    if(str[i]=='#')
        return NULL;
    bt = new BiTree;
    bt ->data =str[i];
    bt->lchild =CreatTree(str,++i);
    bt->rchild =CreatTree(str,++i);
    return bt;
}

void PreOrder(BTree b,int h)
{
     if(b!=NULL)
    {
        cout << b->data<<",";
        PreOrder(b->lchild ,h+1);
        PreOrder(b->rchild ,h+1);
    }
}

void LevelOrder(BTree bt)
{
    BTree p, ptr;
    ptr =new BiTree ;
    ptr ->lchild =NULL;
    ptr ->rchild =NULL;
    ptr ->data ='0';
    p =new BiTree ;
    p->lchild =NULL;
    p->rchild =NULL;
    p->data ='0';
    queue<BTree>q;
    if(bt !=NULL)
    {
        q.push(bt);
        cout <<"1:"<< bt->data << ",";
        ptr =bt ;
    }
    else 
    {
         cout << "NULL";
         return;
    }
    
    int wp1=0;
    int i=2 ;
    int flag =0;
    while(!q.empty())
    {
        p =q.front();
        q.pop();
        if(flag==1)
            cout << p->data<<",";
        flag =1;
        if(p->lchild!=NULL)
        {
            q.push(p->lchild);
        }
        if(p->rchild!=NULL)
        {
            q.push(p->rchild);
        }
        if(p==ptr)
        {
            if(!q.empty())
            {
                cout <<endl <<i<<":";
                ptr =q.back();
            }
            i++;
        }
    }
}

int main()
{
    string str ;
    cin >>str;
    int MaxSize;
    int i=0;
    BTree bt =CreatTree(str ,i);
    LevelOrder(bt);
}

2.1.2 总结解题所用的知识点

先序遍历二叉树
递归的使用
队列内容与树结合,用最右节点帮助换行输出。

2.2 目录树

题目

2.2.1 解题思路及伪代码

解题思路:

结构体使用孩子兄弟链
将读入的数据串按文件和目录的方式分离。(根据题设中的符号“\”仅作为路径分隔符)
将文件和目录内容分别放入新的函数储存。

伪代码:

void CreatTree(Tree&bt ,string str,int i)
{
   定义结构体指针temp,btr;
   初始化temp
    if(i>=str.size())
      return;//路径遍历完毕
   btr=bt; 
   孩子链与兄弟链初始为空状态
  
  
   获取结点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)//目录插入
{
   定义结构体指针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 题目及解题代码

题目来自leecode——树

class Solution {
private:
    int maxSum = INT_MIN;

public:
    int maxGain(TreeNode* node) {
        if (node == nullptr) {
            return 0;
        }
        
        // 递归计算左右子节点的最大贡献值
        // 只有在最大贡献值大于 0 时,才会选取对应子节点
        int leftGain = max(maxGain(node->left), 0);
        int rightGain = max(maxGain(node->right), 0);

        // 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
        int priceNewpath = node->val + leftGain + rightGain;

        // 更新答案
        maxSum = max(maxSum, priceNewpath);

        // 返回节点的最大贡献值
        return node->val + max(leftGain, rightGain);
    }

    int maxPathSum(TreeNode* root) {
        maxGain(root);
        return maxSum;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/solution/er-cha-shu-zhong-de-zui-da-lu-jing-he-by-leetcode-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.2 该题的设计思路及伪代码

时间复杂度:O(N)O(N),其中 N 是二叉树中的节点个数。对每个节点访问不超过 22 次。
空间复杂度:O(N)O(N),其中 N 是二叉树中的节点个数。空间复杂度主要取决于递归调用层数,
3.3 分析该题目解题优势及难点。

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

分类: 数据结构

posted @ 2021-05-05 21:46  落叶满长安  阅读(113)  评论(0编辑  收藏  举报