DS博客作业03--树
| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业03--树 |
| 这个作业的目标 | 学习树结构设计及运算操作 |
| 姓名 | 吕以晴 |
0.PTA得分截图
1.本周学习总结(5分)
1.1 二叉树结构
🌙1.1.1二叉树的2种存储结构
⭐顺序存储结构
二叉树的顺序存储,就是用一组连续的存储单元存放二叉树中的结点。因此,必须把二叉树的所有结点安排成为一个恰当的序列,结点在这个序列中的相互位置能反映出结点之间的逻辑关系,用编号的方法从树根起,自上层至下层,每层自左至右地给所有结点编号,缺点是有可能对存储空间造成极大的浪费,在最坏的情况下,一个深度为k且只有k个结点的右单支树需要2k-1个结点存储空间。
依据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一地反映出结点之间的逻辑关系,这样既能够最大可能地节省存储空间,又可以利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系。
对于一般的二叉树,如果仍按从上至下和从左到右的顺序将树中的结点顺序存储在一维数组中,则数组元素下标之间的关系不能够反映二叉树中结点之间的逻辑关系,只有增添一些并不存在的空结点,使之成为一棵完全二叉树的形式,然后再用一维数组顺序存储。
- 顺序存储结构结构体代码
#define Maxsize 100 //假设一维数组最多存放100个元素
typedef char Datatype; //假设二叉树元素的数据类型为字符
typedef struct
{ Datatype bt[Maxsize];
int btnum;
}Btseq;
⭐链式存储结构
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。
采用链式存储二叉树时,其节点结构由 3 部分构成:
- 指向左孩子节点的指针(Lchild);
- 节点存储的数据(data);
- 指向右孩子节点的指针(Rchild);
(当左孩子或右孩子不存在时,相应指针域值为空(用符号∧或NULL表示)。)
此为一棵普通的二叉树,若将其采用链式存储,则只需从树的根节点开始,将各个节点及其左右孩子使用链表存储即可。
- 链式存储结构结构体代码
typedef struct BiTNode{
TElemType data;//数据域
struct BiTNode *lchild;//左孩子指针
struct BiTNode *rchild;//右孩子指针
}BiTNode,*BiTree;
在某些实际场景中,可能会做 "查找某节点的父节点" 的操作,这时可以在节点结构中再添加一个指针域,用于各个节点指向其父亲节点
- 这样的链表结构,通常称为三叉链表。
🌙1.1.2二叉树的构造
总结二叉树的几种构造方法。分析你对这些构造方法的看法。务必介绍如何通过先序遍历序列和中序遍历序列、后序遍历序列和中序遍历序列构造二叉树。
⭐顺序存储结构建树
/*顺序存储结构建树*/
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;
}
⭐先序遍历建树
/*先序遍历建树*/
void createBiTree(BiTree &T,char array[])
{
char c;
c = array[N];
N++;
if('#' == c)
T = NULL;
else
{ if(T==NULL){
return ;
}
else{
T = new BiTreeNode;
T->data = c;
createBiTree(T->leftChild,array);
createBiTree(T->rightChild,array);
}
}
}
⭐层次遍历建树
/*层次遍历建树*/
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); //右孩子入队
}
}
}
⭐括号法建树
/*括号法建树*/
void CreateBTree(BTNode*& b, char* str)
{
BTNode* St[MAXSIZE];
int top = -1;
BTNode* p;
char ch = *str;
b = NULL;
int k;
while (ch != '\0')
{
switch (ch)
{
case'(':
St[++top] = p;
k = 1;
break;
case')':
top--;
break;
case',':
k = 2;
break;
default:
p = (BTNode*)malloc(sizeof(BTNode));
p->data = ch;
p->lchild = p->rchild = NULL;
if (b == NULL)
b = p;
else
{
switch (k)
{
case 1:St[top]->lchild = p; break;
case 2:St[top]->rchild = p; break;
}
break;
}
}
ch = *(++str);
}
}
⭐先序中序建树
/*先序中序建树*/
struct node
{
char data;
node *lchild, *rchild, *mparent;
};
node* CreateBT1(char* pre, char* in, int n)
{
node* b;
char* p;
int k;
if (n <= 0 || pre == nullptr || in==nullptr) //代码鲁棒性,细节必须注意
return nullptr;
b = (node*)malloc(sizeof(node));
b->data = *pre;
b->mparent = tmpParent;
for (p = in; p < in + n; ++p)
if (*p == *pre)
break;
k = p - in;
b->lchild = CreateBT1(pre + 1, in, k);
b->rchild = CreateBT1(pre + k + 1, p + 1, n - k - 1);
return b;
}
⭐中序后序建树
/*中序后序建树*/
struct node
{
char data;
node *lchild, *rchild;
};
node* CreateBT2(char* post/*指向后序序列开头的指针*/, char* in/*指向中序序列开头的指针*/, int n)
{
char r, *p;
int k;
if (n <= 0 || post== nullptr || in==nullptr) //代码鲁棒性,细节必须注意
return nullptr;
r = *(post + n - 1);
node* b = (node*)malloc(sizeof(node));
b->data = r; //我们要创建的树根节点建立好了
for (p = in; p < in + n; ++p)
if (*p == r) break;
k = p - in; //k是左子树节点数
b->lchild = CreateBT2(post, in, k); //这两个语句最关键
b->rchild = CreateBT2(post + k, p + 1, n - k - 1);
return b;
}
- 没有先序+后序建树,两者虽然都可以确定根的位置,但无法将左右子树分开,无法唯一确定二叉树
🌙1.1.3二叉树的遍历
总结二叉树的4种遍历方式,如何实现。
/*先序遍历递归算法*/
void PreOrder(BTNode *b)
{
if (b!=NULL)
{
printf("%c ", b -> data);//访问根结点
PreOrder(b -> lchild);//对左子树进行先序遍历
PreOrder(b-> rchild);//对右子树进行先序遍历
}
}
/*中序遍历递归算法*/
void InOrder(BTNode *b)
{
if (b!=NULL)
{
InOrder(b -> lchild); //对左子树进行中序遍历
printf("%c ", b-> data); //访问根结点
InOrder(b -> rchild); //对右子树进行中序遍历
}
}
/*后序遍历递归算法*/
void PostOrder(BTNode * b)
{
if (b!=NULL)
{
PostOrder(b ->lchild); //对左子树进行后序遍历
PostOrder(b ->rchild); //对右子树进行后序遍历
printf("%c ",b-> 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线索二叉树
⭐线索二叉树的定义
- 于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树。
- 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
- 线索链表解决了无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题,解决了二叉链表找左、右孩子困难的问题。
⭐线索二叉树的优缺点
- 优点
(1)利用线索二叉树进行中序遍历时,不必采用堆栈处理,速度较一般二叉树的遍历速度快,且节约存储空间。
(2)任意一个结点都能直接找到它的前驱和后继结点。 - 不足
(1)结点的插入和删除麻烦,且速度也较慢。
(2)线索子树不能共用。
⭐线索二叉树的建立
typedef struct node{
ElemType data;//结点数据域
int ltag,rtag;//增加的线索标记
struct node* lchild;//左孩子或线索指针
struct node* rchild;//右孩子或线索指针
}TBTNode;//线索树结点类型定义
⭐中序线索二叉树
-
如果ltag=0,表示lchild为指向孩子结点的指针,如果ltag=1,则表示lchild为线索,指向结点的前驱;
-
如果rtag=0,表示rchild为指向孩子结点的指针,如果rtag=1,则表示rchild为线索,指向结点的后继;
在中序遍历过程中,若要寻找前驱及后继:
- 结点的后继是遍历其右子树时访问的第一个结点,也就是右子树中位于最左下的结点。例如图 3 中结点 * ,后继结点为结点 c ,是其右子树中位于最左边的结点。
- 结点的前趋是左子树最后访问的那个结点。
🌙1.1.5二叉树的应用--表达式树
⭐构建表达式树
/*构建表达式树*/
void InitExpTree(BTree& T, string str) //建表达式的二叉树
{
stack <BTree> num;//存放数字
stack<char> op;//存放运算符
op.push('#');//必须将#进哦op栈
int i = 0;
while (str[i])
{
if (!In(str[i]))//数字
{
T = new BiTNode;
T->data = str[i++];
T->lchild = T->rchild = NULL;//将左右孩子置空
num.push(T);
}
else//运算符
{
switch (Precede(op.top(), str[i]))
{
case'<':op.push(str[i]); i++; break;//运算符比op栈顶低,入op栈
case'=':op.pop(); i++; break;//运算符与op栈顶相等,op栈顶出栈
case'>':T = new BiTNode;//运算符比op栈顶高,新建结点T
T->data = op.top();//取op栈顶运算符
T->rchild = num.top();//数字栈取栈顶,并出栈
num.pop();
T->lchild = num.top();
num.pop();
num.push(T);//将新建结点入栈
op.pop();//op栈出栈
break;
}
}
}
while (op.top() != '#')
{
T = new BiTNode;
T->data = op.top();
op.pop();
T->rchild = num.top();
num.pop();
T->lchild = num.top();
num.pop();
num.push(T);
}
}
⭐计算表达式树
/*计算表达式树*/
double EvaluateExTree(BTree T)//计算表达式树
{
double a, b;
if (T)
{
if (!T->lchild && !T->rchild)
return T->data-'0';//最终结果
a = EvaluateExTree(T->lchild);
b = EvaluateExTree(T->rchild);
switch (T->data)
{
case'+':return a + b; break;
case'-':return a - b; break;
case'*':return a * b; break;
case'/':
if (b == 0)
{
cout << "divide 0 error!" << endl;
exit(0);
}
return a / b; break;
}
}
}
1.2多叉树结构
🌙1.2.1多叉树结构
二叉树中每个结点有一个数据项,最多有两个子节点,如果允许树的每个节点可以有两个以上的子节点,那么这个树就称为n阶的多叉树。
⭐性质
- 每个节点有m个子节点和m-1个键值。
- 每个节点中的键值按升序排列。
- 前i个子节点中的键值都小于第i个键值。
- 后m-1个子节点中的键值都大于第i个键值。
⭐双亲存储结构
-
结点结构:
-
其中data是数据域,存储结点的数据信息
-
parent是指针域,存储该结点的双亲在数组中的下标。
-
由于根结点是没有双亲的,所以我们约定根结点的位置域设置为-1,这也就意味着,我们所有的结点都存有它双亲的位置。
-
由于根结点是没有双亲的,约定根结点的位置位置域为-1.
-
根据结点的parent指针很容易找到它的双亲结点。所用时间复杂度为O(1),直到parent为-1时,表示找到了树结点的根。
-
缺点:如果要找到孩子结点,需要遍历整个结构才行。
-
代码实现
/* 树的双亲表法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int ElemeType;
typedef struct PTNode{ // 结点结构
ElemeType data; //结点数据
int parent; // 双亲位置
}PTNode;
typedef struct { // 树结构
PTNode nodes[MAX_TREE_SIZE]; // 结点数组
int r; // 根的位置
int n; // 结点数
}PTree;
⭐孩子存储结构
- 孩子链表的孩子结点
- 其中child是数据域,用来存储某个结点在表头数组中的下标
- next是指针域,用来存储指向某结点的下一个孩子结点的指针。
-
表头数组的表头结点
-
其中data是数据域,存储某结点的数据信息
-
firstchild是头指针域,存储该结点的孩子链表的头指针。
- 代码实现
/* 树的孩子表示法结构定义*/
#define MAX_TREE_SIZE 100
typedef int ElemeType;
typedef struct CTNode{ // 孩子结点
int child; // 孩子结点的下标
struct CTNode * next; // 指向下一结点的指针
}*ChildPtr;
typedef struct { // 表头结构
ElemeType data; // 存放在数中的结点数据
ChildPtr firstchild; // 指向第一个孩子的指针
}CTBox;
typedef struct { // 树结构
CTBox nodes[MAX_TREE_SIZE]; // 结点数组
int r; // 根的位置
int n; // 结点树
}CTree;
⭐孩子兄弟存储结构
-
结点结构:( 任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟)
-
其中data是数据域,firstchild为指针域,存储该结点的第一个孩子结点的存储地址,
-
rightsib是指针域,存储该结点的右兄弟结点的存储地址。
- 代码实现
/* 树的孩子兄弟表示法结构定义*/
#define MAX_TREE_SIZE 100
typedef int ElemeType;
typedef struct CSNode{
ElemeType data;
struct CSNode * firstchild;
struct CSNode * rightsib;
}CSNode, *CSTree;
🌙1.2.2多叉树遍历
⭐先序遍历
- 在N叉树中,前序遍历指先访问根节点,然后逐个遍历以其子节点为根的子树。
例如,上述三叉树的前序遍历是: A->B->C->E->F->D->G.
⭐后序遍历
- 在N叉树中,后序遍历指前先逐个遍历以根节点的子节点为根的子树,最后访问根节点。
例如,上述三叉树的后序遍历是: B->E->F->C->G->D->A.
⭐层序遍历
- N叉树的层序遍历与二叉树的一致。通常,当我们在树中进行广度优先搜索时,我们将按层序的顺序进行遍历。
例如,上述三叉树的层序遍历是: A->B->C->D->E->F->G.
1.3哈夫曼树
🌙1.3.1哈夫曼树定义
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
- 1.路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
- 2.结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
- 3.树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。
- WPL的计算
WPL=7 * 1 + 5 * 2 + 2 * 3 + 4 * 3
🌙1.3.2哈夫曼树的结构体
/*哈夫曼树结点结构*/
typedef struct {
int weight;//结点权重
int parent, left, right;//父结点、左孩子、右孩子在数组中的位置下标
}HTNode, *HuffmanTree;
🌙1.3.3哈夫曼树构建及哈夫曼编码
结合一组叶子节点的数据,介绍如何构造哈夫曼树及哈夫曼编码。
⭐哈夫曼树构建
对于给定的有各自权值的 n 个结点,构建哈夫曼树有一个行之有效的办法:
- 在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和;
- 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;
- 重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。
- 图中(A)给定了四个结点a,b,c,d,权值分别为7,5,2,4
- 第一步如(B)所示,找出现有权值中最小的两个2 和 4
- 相应的结点 c 和 d 构建一个新的二叉树,树根的权值为 2 + 4 = 6
- 同时将原有权值中的 2 和 4 删掉,将新的权值 6 加入
- 进入(C),重复之前的步骤
- 直到(D)中,所有的结点构建成了一个全新的二叉树,这就是哈夫曼树
1.4并查集
🌙1.4.1定义
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。
并查集解决什么问题,优势在哪里?
并查集的结构体、查找、合并操作如何实现?
🌙1.4.2主要操作
⭐初始化
把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
假如有编号为1, 2, 3, ..., n的n个元素,我们用一个数组fa[]来存储每个元素的父节点(因为每个元素有且只有一个父节点,所以这是可行的)。一开始,我们先将它们的父节点设为自己。
int fa[MAXN];
inline void init(int n)
{
for (int i = 1; i <= n; ++i)
fa[i] = i;
}
⭐查找
查找元素所在的集合,即根节点。
我们用递归的写法实现对代表元素的查询:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。
int find(int x)
{
if(fa[x] == x)
return x;
else
return find(fa[x]);
}
⭐合并
将两个元素所在的集合合并为一个集合。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
先找到两个集合的代表元素,然后将前者的父节点设为后者即可。
inline void merge(int i, int j)
{
fa[find(i)] = find(j);
}
1.5谈谈你对树的认识及学习体会。
🌙1.5.1重难点
- 完全二叉树只有最后一层才可能出现单分支,且单分支只能为左分支。
- 非空二叉树上叶子结点个数等于双分支结点个数+1
- 高度为k的二叉树,最多有2^k-1,最少有k个结点
- 有n个结点的完全二叉树深度为,[log2n]+1
- 满二叉树的结点个数为:2^n-1 (n为树的高度)
🌙1.5.2销毁二叉树
void DestroyBTree(BTree bt)
{
if (bt != NULL)//一定要用后序遍历销毁
{
DestroyBTree(bt->lchild);
DestroyBTree(bt->rchild);
delete bt;
}
}
🌙1.5.3基础计算题题解
- 题一
题解:
设T的叶子结点个数为y,
总分支-1=总度数,即 20+10+1+10+y-1 = 20×4+10×3+1×2+10×1
- 题二
题解:
- 哈夫曼树的性质:(节点为的度数为0 表示 n0,以此类推)
①哈夫曼树中只存在度为2和度为0的节点,所以n1=0。
②哈夫曼树中,度为0和度为2的节点关系:n2=n0-1
由以上两个性质,可得
n0+n2=115 =>
n0+n0-1=115 =>
n0=(115+1)/2=58
2.PTA实验作业(4分)
此处请放置下面2题代码所在码云地址(markdown插入代码所在的链接)。如何上传VS代码到码云
2.1 二叉表达式树
输出二叉树每层节点、二叉表达式树、二叉树叶子结点带权路径长度和 三题自选一题介绍。
void InitExpTree(BTree& T, string str) //建表达式的二叉树
{
stack <BTree> num;
stack<char> op;
op.push('#');
int i = 0;
while (str[i])
{
if (!In(str[i]))//数字
{
T = new BiTNode;
T->data = str[i++];
T->lchild = T->rchild = NULL;
num.push(T);
}
else
{
switch (Precede(op.top(), str[i]))
{
case'<':op.push(str[i]); i++; break;
case'=':op.pop(); i++; break;
case'>':T = new BiTNode;
T->data = op.top();
T->rchild = num.top();
num.pop();
T->lchild = num.top();
num.pop();
num.push(T);
op.pop();
break;
}
}
}
while (op.top() != '#')
{
T = new BiTNode;
T->data = op.top();
op.pop();
T->rchild = num.top();
num.pop();
T->lchild = num.top();
num.pop();
num.push(T);
}
}
double EvaluateExTree(BTree T)//计算表达式树
{
double a, b;
if (T)
{
if (!T->lchild && !T->rchild)
return T->data-'0';//最终结果
a = EvaluateExTree(T->lchild);
b = EvaluateExTree(T->rchild);
switch (T->data)
{
case'+':return a + b; break;
case'-':return a - b; break;
case'*':return a * b; break;
case'/':
if (b == 0)
{
cout << "divide 0 error!" << endl;
exit(0);
}
return a / b; break;
}
}
}
🌙2.1.1 解题思路及伪代码
🌙2.1.2 总结解题所用的知识点
- 栈的申请,建立,入栈,出栈
- switch多分支选择语句
- 动态空间申请
- if.while语句
2.2 目录树
#include<iostream>
#include<algorithm>
using namespace std;
typedef struct TNode{
bool type;
string name;
struct TNode *Child_file[500];
struct TNode *Child_mulu[500];
int file;
int mulu;
}TNode,*Root;
int n;
Root root;
TNode *Search(Root Node,string s){
for(int i = 0;i < Node->mulu;i++)
if(Node->Child_mulu[i]->name == s)return Node->Child_mulu[i];
return NULL;
}
TNode *Creat_Mulu(Root &head,string s){
TNode *p = new TNode;
p->name = s;
p->type = true;
p->mulu = 0;
p->file = 0;
head->Child_mulu[head->mulu++] = p;
return p;
}
void Creat_File(Root &head,string s){
TNode *p = new TNode;
p->name = s;
p->type = false;
head->Child_file[head->file++] = p;
}
void Deal(string s){
string str = "";
TNode *Pre = root;
int Time = 0;
Root head = root;
for(int i = 0;i < s.size();i++){
if(s[i] != '\\')str += s[i];
else {
Pre = Search(head,str);
if(!Pre)head = Creat_Mulu(head,str);
else head = Pre;
str = "";
}
}
if(s[s.size() - 1] != '\\'){
Pre = Search(head,str);
if(!Pre)Creat_File(head,str);
}
}
void Initial(){
root = new TNode;
root->name = "root";
root->file = 0;
root->mulu = 0;
root->type = true;
cin>>n;
for(int i = 0;i < n;i++){
string s;
cin>>s;
Deal(s);
}
}
bool cmp(TNode *a,TNode *b){
return a->name < b->name;
}
void Show(Root head,int n){
for(int j = 0;j < n;j++)cout<<" ";
cout<<head->name<<endl;
if(head->type == false)return;
sort(head->Child_file,(head->Child_file) + head->file,cmp);
sort(head->Child_mulu,(head->Child_mulu) + head->mulu,cmp);
for(int i = 0;i < head->mulu;i++)Show(head->Child_mulu[i],n + 1 );
for(int i = 0;i < head->file;i++)Show(head->Child_file[i],n + 1);
}
int main(){
Initial();
Show(root,0);
}
🌙2.2.1 解题思路及伪代码
🌙2.2.2 总结解题所用的知识点
- 字符数组
- 动态空间申请
- for.if语句
- 指针的运用
3.阅读代码(0--1分)
3.1 题目及解题代码
🌙3.1.1 题目
🌙3.1.2 解题代码
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;
}
};
3.2该题的设计思路及复杂度分析
🌙3.2.1 解题思路
首先,考虑实现一个简化的函数 maxGain(node)
,该函数计算二叉树中的一个节点的最大贡献值,具体而言,就是在以该节点为根节点的子树中寻找以该节点为起点的一条路径,使得该路径上的节点值之和最大。
具体而言,该函数的计算如下。
空节点的最大贡献值等于0。
非空节点的最大贡献值等于节点值与其子节点中的最大贡献值之和(对于叶节点而言,最大贡献值等于节点值)。
例如,考虑如下二叉树。
-10
/ \
9 20
/ \
15 7
叶节点 9、15、7 的最大贡献值分别为 9、15、7。
得到叶节点的最大贡献值之后,再计算非叶节点的最大贡献值。节点 20 的最大贡献值等于 20+max(15,7)=35,节点 -10的最大贡献值等于 -10+−10+max(9,35)=25。
上述计算过程是递归的过程,因此,对根节点调用函数 maxGain,即可得到每个节点的最大贡献值。
根据函数 maxGain 得到每个节点的最大贡献值之后,如何得到二叉树的最大路径和?对于二叉树中的一个节点,该节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值,如果子节点的最大贡献值为正,则计入该节点的最大路径和,否则不计入该节点的最大路径和。维护一个全局变量 maxSum 存储最大路径和,在递归过程中更新 maxSum 的值,最后得到的 maxSum 的值即为二叉树中的最大路径和。
🌙3.2.2 复杂度分析
-
时间复杂度:O(N),其中 N 是二叉树中的节点个数。对每个节点访问不超过 2 次。
-
空间复杂度:O(N),其中 N 是二叉树中的节点个数。空间复杂度主要取决于递归调用层数,最大层数等于二叉树的高度,最坏情况下,二叉树的高度等于二叉树中的节点个数。
3.3分析该题目解题优势及难点
-
解题优势:先考虑实现一个简化的函数,接着通过枚举逐渐推出题解。
-
难点:理解题意和转化题意,可以参考数组的最大子数组和的思路。