这个作业属于哪个班级 | 数据结构 |
---|---|
这个作业的地址 | DS博客作业03--树 |
这个作业的目标 | 学习树结构设计及运算操作 |
姓名 | 曹卉潼 |
0.PTA得分截图
1.本周学习总结(5分)
1.1 二叉树结构
1.1.1 二叉树的2种存储结构
顺序存储结构
二叉树的顺序存储结构就是用一组地址连续的存储单元来存放二叉树的数据元素,因此必须确定好树种各数据元素的存放次序,使得各数据元素在这个存放次序中的相互位置能反映出数据元素之间的逻辑关系。
先用空结点把二叉树补成完全二叉树,然后对结点编号,不使用下标为0的元素,第i个结点:左孩子结点编号为2i,右孩子编号结点为2i+1,其父节点的编号为⌊i/2⌋
- 优点:很适用于完全二叉树
- 缺点:
1.对于普通二叉树来说,在最坏的情况下,一个深度为k且只有k个结点的 单支树却需要2^k-1的一维数组,空间利用率太低。
2.数组,查找、插入、删除不方便 - 定义:
typedef ElemType sqBTree[MaxSize];
链式存储结构
优缺点:
- 空间利用效率高,方便插入和删除;
- 不容易访问结点的双亲;
结构体定义:
typedef struct TNode * Position;
typedef Position BTree;
struct TNode
{
ElementType data;//结点数据
BTree lchild;//指向左孩子;
BTree rchild;//指向右孩子
};
1.1.2 二叉树的构造
先序遍历建树
BTree CreatTree(string str,int &i)//每一次递归都改变i的值,以此达到先建立根结点再建立左子树最后建立右子树的目的;
{
BTree bt;
if(i>len-1)
return NULL;
if(str[i]=='#')
return NULL;
bt = new TNode;
bt->data=str[i];
bt->lchild= CreatTree(str,++i);
bt->rchild= CreatTree(str,++i);
return bt;
}
二叉树的顺序存储结构转换成二叉链
BTree CreateTree(string str, int i)
{
BTree BT;
if (i > str.size())
return NULL;
if (str[i] == '#')
return NULL;
BT = new BTNode;
BT->data = str[i];
BT->lchild = CreateTree(str, 2 * i);//如果i从0开始,2*i+1
BT->rchild = CreateTree(str, 2 * i+1);//2*i+2
return BT;
}
层次遍历建二叉树
初始化队伍,创建根结点,将根结点进队;
while(队伍不空)
队伍中出列一个节点T;
去字符str[i];
if(str[i]=='#')
T->lchild=NULL;
else
生成T的左孩子结点,值为str[i],把T->lchild入队;
end if
取str下一个字符;
if(str[i]=='#')
T->rchild=NULL;
else
生成T的右孩子结点,值为str[i],把t->rchild入队;
end if
end while
1.1.3 二叉树的遍历
先序遍历:根结点->左子树->右子树
void PreOrder(BTree bt)
{
if(bt!=NULL)
{
cout<<bt->data<<" ";
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
中序遍历:左子树->根结点->右子树
void InOrder(BTree bt)
{
if(bt!=NULL)
{
InOrder(bt->lchild);
cout<<bt->data<<" ";
InOrder(bt->rchild);
}
}
后序遍历:左子树->右子树->根结点
void PostOrder(BTree bt)
{
if (bt != NULL)
{
PostOrder(bt->lchild);
PostOrder(bt->rchild);
cout << bt->data << " ";
}
}
层次遍历:
- 伪代码思路
访问根结点,如果不为空则入队;
while(队列不为空)
队列中出列一个节点,访问它;
如果他的左孩子不为空,则左孩子入队;
如果他的右孩子不为空,则右孩子入队;
end while
- 代码:
void DisBTree(BTree bt)
{
BTree bt;
queue<BTree>q;
if (bt != NULL)
q.push(bt);
else
{
cout << "NULL";
return;
}
while(!q.empty())
{
bt = q.front();
q.pop();
cout << " " << bt->data;
if (bt->lchild)
q.push(bt->lchild);
if (bt->rchild)
q.push(bt->rchild);
}
}
1.1.4 线索二叉树
结构体定义
typedef struct node
{
ElemType data;
int ltag,rtag;//用于判断是否有左右孩子;
struct node* lchild;
struct node* rchild;
}
- 若结点有左子树(ltag=0),则lchild指向其左孩子;否则(ltag=1)lchild指向其直接前驱(即线索);
- 若结点有右子树(rtag=0),则rchild指向其右孩子,否则(rtag=0)rchild指向其直接后继(即线索);
每个结点的存储结构为:
1.1.5 二叉树的应用--表达式树
介绍表达式树如何构造
如何计算表达式树
1.2 多叉树结构
1.2.1 多叉树结构
1.双亲存储结构
找父亲容易,找孩子难
结构体定义
typedef struct
{
ElemTypde data;//结点的值;
int parent;//存放双亲的位置
}PTree[MAXSIZE];
2.孩子链存储结构
找孩子容易,找父亲难,空指针多
结构体定义
typedef struct node
{
ElemTypde data;//结点的值;
struct node *sons[MaxSons];//保存孩子指针
}TSonNode;
3.孩子兄弟链存储结构
固定两个指针域,找父亲难
结构体定义
typedef struct tnode
{
ElemType data;
struct tnode *son ;//指向孩子;
struct tnode *brother;//指向兄弟;
}TNode,*Tree;
1.2.2 多叉树遍历
- 先根遍历:若树补为空,则先访问根结点,然后依次先根遍历各棵子树;
- 后根遍历:若树不为空,则先访问后根遍历各子树,然后访问根结点;
- 层次遍历:若树不为空,则自上而下,自左而右访问树中每个结点;
1.3 哈夫曼树
1.3.1 哈夫曼树定义
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达
到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
带权路径长度之和(WPL)=从根结点到该结点之间的路径长度与该结点上权的乘积之和
1.3.2 哈夫曼树的结构体
教材是顺序存储结构,也可以自己搜索资料研究哈夫曼的链式结构设计方式。
typedef struct //顺序存储结构
{
char data;//节点值;
float weight;//权重;
int parent;//双亲结点;
int lchild,rchild;
}HTNode;
1.3.3 哈夫曼树构建及哈夫曼编码
结合一组叶子节点的数据,介绍如何构造哈夫曼树及哈夫曼编码。
(可选)哈夫曼树代码设计,也可以参考链式设计方法。
伪代码思路
void CreatHCode(HTNode ht[],HCode hcd[],int n)
{
for i=0 to n
取一叶结点ht[i];
从叶结点开始往树根结点开始编码(从下而上,在数组体现为从第n个位置开始编码)
while(还未遍历到树根结点)
若结点为父节点的右孩子,编码为1;
若结点为父节点的左孩子,编码为0;
结点指针指向父节点,进行下一次判断
end while
记录编码停止的位置
end for
}
在远程通讯中,要将待传字符转换成二进制的字符串,为了统一我们要对每个字符进行编码,不同的字符对应不同的编码,其长度也不相同。为了使总编码的长度最短,提高传递效率,我们应该将使用次数最多的字符的编码设置的尽量短一点,且不能使任一字符的编码为另一个字符编码的前缀,否则一段二进制编码就会有多种不同的解法,得到的信息很可能不是我们所期望的;这里我们可以用哈夫曼树来编码。
1.4 并查集
并查集定义:
在并查集中,每个分离集合对应的一棵树,称为分离集合树,整个并查集也就是一棵分离集合森林。
- 集合查找:在一棵高度较低的树中查找根节点的编号,所花时间较少,同一个集合标志就是根是一样的;
- 集合合并:两棵分离集合树A和B,高度分别为ha和hb,若ha>hb,应将b树作为a树的子树。将高度较小的分离集合树作为子树,得到的新的分离集合树c高度为hc=Max{ha,hb};
并查集结构体定义
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;
t[i].parent = i;
}
}
查找一个元素所属集合
int FIND_SET(UFSTree t[], int x)
{
if (x != t[x].parent)//双亲不是 自己
return(FIND_SET(t, t[x].parent));//递归双亲找x
else
return x;
}
两个元素各种所属集合的合并
void UNION(UFSTree t[], int x, int y)
{
x = FIND_SET(t, x);//查找x所在分离集合树的情况
y = FIND_SET(t, y);//查找y所在分离集合树的情况
if (t[x].rank > t[y].rank)
t[y].parent = x;
else
{
t[x].parent = y;
if (t[x].rank == t[y].rank)//秩相同时
t[y].rank++;
}
}
1.5.谈谈你对树的认识及学习体会
树结构是一对多的,相对于之前的链表、栈、队列有更多种遍历方式,需要弄明白前中后序三种基本遍历,并能运用起来解决不同的问题。
写PTA过程中很多都用到了递归,运用递归可以减少很多代码量,但是如何设计递归也是比较难的,感觉好难自己写出来。。
线索二叉树那部分内容其实还没理解诶,孩子兄弟结构理解不够透,运用较困难。
感觉树这部分内容比较多题目也比较难,还要好好研究啊。
2.PTA实验作业(4分)
2.1 二叉树叶子结点带权路径长度和
2.1.1 解题思路及伪代码
Tree CreateTree(string str, int i)
{
设定Tree变量T
if(i>str.size()-1)return NULL;
if(是'#',是空节点)return NULL;
为T开辟一个空间
T->data=str[i];
T的lchild应该是第2*i的元素
rchild应该是第2*i+1的元素
return T;
}
void GountWpl(BTree T,int &wpl,int i)
{
定义h表示深度
先判断是否是叶子节点
if(T->Left==NULL&&T->Right==NULL)
{
wpl=深度*当前data的值
深度归零
}
h++
若是空节点,return
非空非叶
递归访问其左右子树
GetWpl(wpl,T->Left,i+1);
GetWpl(wpl,T->Right,i+1);
代码
2.1.2 总结解题所用的知识点
2.2 目录树
2.2.1 解题思路及伪代码
typedef struct node*BTree;
typedef struct node
{
string name;
BTree Catalog;
BTree Brother;
Bool flag;
}BTnode;
void DealStr(string str, BTree bt)
{
while(str.size()>0)
{
查找字符串中是否有’’,并记录下位置pos
if(没有)
说明是文件,则进入InsertFile的函数
else
说明是目录,则进入Insertcatalog的函数里
bt=bt->catalog;
同时bt要跳到下一个目录的第一个子目录去,因为刚刚Insertcatalog的函数是插到bt->catalog里面去
while(bt!NULL&&bt->name!=name)
bt=bt->Brother;//找到刚刚插入的目录,然后下一步开始建立它的子目录
str.erase(0, pos + 1);把刚插进去的目录从字符串中去掉
}
}
代码
2.2.2 总结解题所用的知识点
- 运用孩子兄弟存储结构建树
- 注意根目录与子目录的插入位置关系
3.阅读代码(0--1分)
3.1 题目及解题代码
题目
解题代码
class Solution {
public:
bool helper(TreeNode* A, TreeNode* B) {
if (A == NULL || B == NULL) {
return B == NULL ? true : false;
}
if (A->val != B->val) {
return false;
}
return helper(A->left, B->left) && helper(A->right, B->right);
}
bool isSubStructure(TreeNode* A, TreeNode* B) {
if (A == NULL || B == NULL) {
return false;
}
return helper(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
}
};
3.2 该题的设计思路及伪代码
请用图形方式展示解决方法。同时分析该题的算法时间复杂度和空间复杂度。
3.2.1设计思路
利用递归的方法,当两指针都不为空的时候检查两者的数据域是否相等,若当前数据域相等则继续比较两者的左孩子,右孩子,若不相等返回false或者。当两指针都指向空的时候代表两者该点没有储存数值,返回true。当两者指针只有一方为空的时候,代表出现了另一方没有的节点,返回false
- 时间复杂度:O(n),n为节点数
- 空间复杂度:O(h),h为树的高度
3.2.2伪代码
bool isSameTree(TreeNode* p,TreeNode* q)
{
if(q为空且p也为空)返回true;
end if
if(p不为空或者q不为空)返回false;
end if
返回(p->val==q->val)&&(isSameTree(p->left,q->left)&&isSameTree(p->right,q->right));
}
3.3 分析该题目解题优势及难点。
- 这题也没什么特别难的地方,主要是递归比较难写
- 值得注意的是一定要小心空树的情况,不然容易崩溃
- 代码简洁明了,利用递归减少代码的长度,三种情况容易看得清楚,易于理解
- 使用bool类型,更加方便简洁,不需要设置什么flag之类的东西,直接返回输出即可