DS博客作业03--树
0.PTA得分截图
1.本周学习总结(0-5分)
1.1 总结树及串内容
1.1.1串的BF\KMP算法
1.1.1.1BF算法
算法介绍
- 若现在两个字符串S和T,若要在S串中寻找T串,则S串称为目标串,T串成为模式串。
- 取T的第一个字符和S的第一个字符比较,若匹配则再取S的第二个字符和T的第二个字符比较,若不匹配则取S的第二个字符和T的第一个字符从头比较,以此类推,直到在T中完整匹配出S串。
- 该算法时间复杂度时间复杂度较高,最坏的情况可达O(m*n)(m,n分别为S和T的字符串长度),因此此算法又称蛮力算法。
图示助通
1.目标串S的第一位开始与模式串T第一位匹配,B与l不匹配,匹配失败
2.目标串S第二位开始与模式串T第一位匹配,i与l不匹配,匹配失败
3.目标串S第三位开始与模式串T第一位匹配,S与T四个字母全部匹配,匹配成功
代码实现
int StrCompare_BF(string S, string T, int pos = 0)
{
int i = pos; //i用于主串S的起始位置
int j = 0; //子串的起始位置
int lenS = S.size();
int lenT = T.size();
while (i < lenS&&j < lenT)
{
if (S[i] == T[j])
{
++i;
++j;
}
else
{
//当字符不匹配的时候,i回溯,移动到下一位
i = i - j + 1;
j = 0;
}
}
if (j >= lenT)
{
return i - j; //匹配成功则返回T在S串中出现的第一个位置
}
else
{
return -1; //未成功
}
}
1.1.1.2KMP算法
算法介绍
步骤图解
1.1.2二叉树相关梳理
1.1.2.1存储结构
顺序表存储(数组)
-
图示助通(完全二叉树和非完全二叉树)
-
说明
顺序存储就是创建一个数组来存放树结点(注意到数组的第一个空间是舍弃不用的),依次从下标1开始按第一层,第二层,第三层的顺序逐个存储结点数据,那么我们由上面两种树的存储情况来看,完全二叉树用数组来存储是较为合理的,但不完全二叉树用数组来存放会浪费很多空间,如果在此时树的深度很大很大,那么浪费的空间是难以想象的,此时更适合用二叉链来存放,由下面介绍。
二叉链表存储
结构体定义
typedef struct BTNode
{
int data;
struct BTNode* lchild; //结点数据域
struct BTNode* rchild; //左右孩子指针
}BTNode,*BiTree;
图示助通(以上面非完全二叉树为例)
1.1.2.2建法
根据其先序遍历递归
- 法一:函数无返回
void CreateTree(BiTree& T, string str, int &i) //i是引用变量,值的改变是全局的
{
int len = str.size();
if ( i >= len) //必须先判断str的遍历是否到头,否则会出现越界访问
{
T = NULL;
return;
}
if (str[i] == '#')
{
T = NULL;
return;
}
T = new BiTNode;
T->data = str[i];
CreateTree(T->lchild, str, ++i);
CreateTree(T->rchild, str, ++i);
}
- 法二:函数返回根结点
BiTree CreateTree(string str, int &i)
{
BiTree T;
int len = str.size();
if ( i >= len)
{
return NULL;
}
if (str[i] == '#')
{
return NULL;
}
T = new BiTNode;
T->data = str[i];
T->lchild=CreateTree( str, ++i);
T->rchild=CreateTree( str, ++i);
}
根据其顺序存储(层次)结构
-
首先将树的空结点补齐
-
再用数组来层次存储
ABCD#EF#GH##I######(可理解为层次存储,#代表空结点) -
代码
BiTree CreateTree(string str, int &i)
{
BiTree T;
int len = str.size();
if ( i >= len)
{
return NULL;
}
if (str[i] == '#')
{
return NULL;
}
T = new BiTNode;
T->data = str[i];
T->lchild=CreateTree( str, i*2); //与先序建树的区别
T->rchild=CreateTree( str, i*2+1);
}
根据先,中序还原树
-
图示
-
具体代码
BTree CreateTree(char *pre,char *in,int n) //pre和in是分别指向先序字符串和中序字符串第一个字符的指针
{
BTNode *s;
char *op;
int k;
if(n<=0)
{
return NULL;
}
s=new BTNode;
s->data=*pre;
for(op=in;p<in+n;p++) //在中序序列中寻找根结点
{
if(*op==*pre)
{
break;
}
}
k=op-in;
s->lchild=CreateTree(pre+1,in,k); //递归往下生成结点
s->lchild=CreateTree(pre++k+1,op+1,n-k-1);
return s;
}
根据中,后续还原树
- 图示
- 具体代码
BTree CreateTree(char *post,char *in,int n) //post和in是分别指向先序字符串和中序字符串第一个字符的指针
{
BTNode *s;
char *op;
int k;
if(n<=0)
{
return NULL;
}
s=new BTNode;
s->data=*(post+n-1);
for(op=in;p<in+n;p++) //在中序序列中寻找根结点
{
if(*op==*(post+n-1)
{
break;
}
}
k=op-in;
s->lchild=CreateTree(post,in,k); //递归构造左右子树
s->lchild=CreateTree(post+k,op+1,n-k-1);
return s;
}
1.1.2.3遍历(递归遍历和层次遍历)
以此二叉树为例
递归遍历
- 先序
遍历结果:ABDGCEHFI
代码
void PreOrder(BTree T)
{
if(T==NULL)
{
return; //递归口退出递归
}
else
{
cout<<T->data;
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
- 中序
遍历结果:DGBAHECFI
代码
void InOrder(BTree T)
{
if(T==NULL)
{
return; //递归口退出递归
}
else
{
InOrder(T->lchild);
cout<<T->data;
InOrder(T->rchild);
}
}
- 后序
遍历结果:GDBHEIFCA
代码
void PostOrder(BTree T)
{
if(T==NULL)
{
return; //递归口退出递归
}
else
{
PostOrder(T->lchild);
PostOrder(T->rchild);
cout<<T->data;
}
}
层次遍历
遍历结果:ABCDEFGHI
思路图解:
代码
void LevelOrder(BTree T)
{
queue<BTree>Q;
BTree Temp;
if(T!=NULL) //先将根结点入队
{
Q.push(T);
}
else
{
return;
}
while(!Q.empty())
{
Temp=Q.front();
if(Temp->child!=NULL)
{
Q.push(Temp->child);
}
if(Temp->lchild!=NULL)
{
Q.push(Temp->lchild);
}
cout<<Temp->data;
Q.pop();
}
}
总结:先,中,后序遍历二叉树的区别仅仅在于输出结点先后顺序的控制,所以整合为递归遍历,与层次遍历区别开来。
1.1.2.4应用
1.1.3树的相关梳理
1.1.3.1树的结构
双亲表示法
- 结构体定义
typedef struct PTNode
{
ElemType data;
int parent; //指向双亲的下标
}PTNode; //结点结构体
typedef struct
{
PTNode nodes[MAXSIZE];
int root; //指向根结点的下标
int count; //结点数
}PTree;
- 概念理解:
1.采用顺序表(数组)存储各个结点。
2.给各个结点附上一个变量parent,用来记录双亲在数组中的下标 - 图示助通:
孩子表示法
- 结构体定义:
typedef struct CTNode
{
int child; //指向第一个孩子
struct CTNode *next; //指向后续孩子
}*ChildPtr;
typedef struct
{
ElemType data;
ChildPtr *firstchild; //指向第一个孩子
}CTBox; //定义表头结构体
typedef struct
{
PTNode nodes[MaxSize]; //存储结点
int root; //指向根结点的下标
int count; //结点数
}CTree; //定义树结构体
- 概念理解:
1.存储树采用的是 "顺序表+链表" 的组合结构
2.从根结点开始,由数组存储各个结点。
3.给各个结点分配一个链表,若有孩子,则链表节点存孩子在数组中的下标,下一个链表节点是第二个孩子,以此类推;若无孩子,则该链表就是NULL。 - 图示助通:
孩子兄弟表示法
- 结构体定义:
#define ElemType char
typedef struct CSNode{
ElemType data;
struct CSNode * firstchild,*nextsibling; //指向第一个孩子和指向相邻的兄弟
}CSNode,*CSTree;
- 概念理解:
1.采用的是链式存储结构。
2.结构体成员有data(数据域),nextbro(指向双亲相同的兄弟结点),firstchild(指向第一个孩子结点) - 图示助记
1.1.4线索二叉树
1.1.4.1线索树的注意点
- 空的左孩子指针指向该结点的前驱;空的右孩子指针指向该结点的后继。这种附加的指针值称为线索,带线索的二叉树称为线索二叉树。
- 为了标志左右孩子指针的空与不空,在结构体定义中增加了两位成员ltag,rtag,若左孩子指针为空,则ltag值赋为1,不空则为0。
- 在不同的遍历次序下,二叉树中的每个结点一般有不同的前驱和后继。因此,线索二叉树又分为前序线索二叉树、中序线索二叉树和后序线索二叉树3种
1.1.4.2线索树的结构体定义
typedef struct BTNode
{
struct BTNode* lchild,rchild;
int ltag,rtag;
}
1.1.4.3举例(先序,中序,后序)
- 以中序为例
1.1.5数的特殊应用
1.1.5.1哈夫曼树
字符编码问题的提出
- 若有一段字符为abbaca,出于某种需求将其用二进制(0和1)编码
- 若分别表示为a(01),b(000),c(00),则字符串完整编码为01000000010001
- 问题便来了,若将第二步二进制编码解码为原字符,字符串acccaca也符合条件,那么如何使解码唯一呢
- 问题二:如何使解码唯一的同时,占用二进制为最少呢
问题的解决 - 为使得编码唯一,某个字符二进制编码的后缀不能为另一个字符的前缀,发现用二叉树的叶子结点表示这些字符,从根结点开始,在寻找字符的过程中,往左走编码为0,右则为1。
- 为使得编码长度最短,则在字符串中出现次数越少的字符所在层数应该越高,所以在建树过程中,字符出现的次数作为其权重,在所有权重中寻找最小的两个,组成一棵树,它们分别为叶子结点,根结点权重为它们权重的和,再将根结点的权重加入原来的比较中,直到最后只剩一个结点。
图示助通(字符串addbbaddcad为例)
1.1.5.2并查集
引例
1.2.学习体会
本章的树相较于上一章的栈和队列来说,我个人认为难度会稍大一点,因为树的多个操作和应用需要进行递归运算,这对于递归函数结构的设计和参数传递的精准度要求较高,将考验我们的抽象思维和逻辑思维,此时若感觉到困难,可借助手动步骤演示来帮助函数设计。那么对于学好树的内容来说,除了上面的建议之外,我们还应该要熟记许多小知识和基操:概念如满二叉树,完全二叉树,树的高度,度,完全二叉树的叶子结点个数计算,基操如三序遍历,层次遍历,算数高度,层数,wpl。
2.阅读代码(0--5分)
找4份优秀代码,理解代码功能,并讲出你所选代码优点及可以学习地方。主要找以下类型代码:
考研题
PAT\天梯赛题目
ACM题解
leecode--树
注意:不能选教师布置在PTA的题目。完成内容如下。
2.1 题目及解题代码
可截图,或复制代码,需要用代码符号渲染。题目截图后一定要清晰。
2.1.1 该题的设计思路
链表题目,请用图形方式展示解决方法。同时分析该题的算法时间复杂度和空间复杂度。
2.1.2 该题的伪代码
文字+代码简要介绍本题思路
2.1.3 运行结果
网上题解给的答案不一定能跑,请把代码复制自己运行完成,并截图。
2.1.4分析该题目解题优势及难点。
评分注意
本学期,博客作业重点会在知识总结及代码阅读内容。代码阅读部分务必按照要求详细介绍解题思路。回答简单应付,博客书写没有看出作者对代码理解,则得0分。
------------恢复内容结束------------
*------------恢复内容结束------------