这个作业属于哪个班级 | 数据结构--网络2011/2012 |
---|---|
这个作业的地址 | DS博客作业03--树 |
这个作业的目标 | 学习树结构设计及运算操作。 |
姓名 | 罗发槺 |
0.PTA得分截图
1.本周学习总结(5分)
1.1 二叉树结构
1.1.1 二叉树的2种存储结构
- 1.顺序存储
- 二叉树的顺序存储结构就是用一组地址连续的存储单元来存放二叉树的数据元素,因此必须确定好树中
各数据元素的存放次序,是的个元素再这个存放次序中的相互位置能反映出数据元素之间的逻辑关系。
结构体定义:
- 二叉树的顺序存储结构就是用一组地址连续的存储单元来存放二叉树的数据元素,因此必须确定好树中
typedef ElemType sqBTree[MaxSize];
- 2.链式存储
- 二叉树的链式存储结构是指一个链表来存储一颗二叉树,二叉树中的每一个节点用链表中的一个节点来存储,二叉树链式存储结构如下:
- 二叉树的链式存储结构是指一个链表来存储一颗二叉树,二叉树中的每一个节点用链表中的一个节点来存储,二叉树链式存储结构如下:
结构体定义:
typedef struct TNode * Position;
typedef Position BTree;
struct TNode
{
ElementType data;//结点数据
BTree lchild;//指向左孩子;
BTree rchild;//指向右孩子
};
-
3.优缺点
- 顺序存储
优点:读取某个指定的节点的时候效率比较高O(0)
缺点:会浪费空间(在非完全二叉树的时候) - 链式存储
优点:读取某个指定节点的时候效率偏低O(nlogn)
缺点:相对二叉树比较大的时候浪费空间较少
- 顺序存储
-
总结
-
二叉树的顺序存储,寻找后代节点和祖先节点都非常方便,但对于普通的二叉树,顺序存储浪费大量的存
储空间,同样也不利于节点的插入和删除。因此顺序存储一般用于存储完全二叉树。 -
链式存储相对顺序存储节省存储空间,插入删除节点时只需修改指针,但寻找指定节点时很不方便。不过
普通的二叉树一般是用链式存储结构。
-
1.1.2 二叉树的构造
总结二叉树的几种构造方法。分析你对这些构造方法的看法。务必介绍如何通过先序遍历序列和中序遍历序列、后序遍历序列和中序遍历序列构造二叉树。
- 先序遍历构造
BTree CreateBTree(string str, int &i)
{
if (str[i] == '#')
{
i++;
return NULL;
}
BTree BT;
BT = new BTNode;
BT->data = str[i++];
BT->lchild = CreateBTree(str,i);
BT->rchild = CreateBTree(str, i);
return BT;
}
- 中序遍历构造
BTree CreateBTree(string str, int &i)
{
if (str[i] == '#')
{
i++;
return NULL;
}
BTree BT;
BT = new BTNode;
BT->lchild = CreateBTree(str,++i);
BT->data = str[i];
BT->rchild = CreateBTree(str, +++i);
return BT;
}
- 后序遍历构造
BTree CreateBTree(string str, int &i)
{
if (str[i] == '#')
{
i++;
return NULL;
}
BTree BT;
BT = new BTNode;
BT->lchild = CreateBTree(str,++i);
BT->rchild = CreateBTree(str, +++i);
BT->data = str[i];
return BT;
}
1.1.3 二叉树的遍历
- 先序遍历
void PreorderPrintLeaves( BinTree BT ){
if(!BT)
return;
printf(" %c", BT->Data);
PreorderPrintLeaves(BT->Left);
PreorderPrintLeaves(BT->Right);
}
- 中序遍历
void PreorderPrintLeaves( BinTree BT ){
if(!BT)
return;
PreorderPrintLeaves(BT->Left);
printf(" %c", BT->Data);
PreorderPrintLeaves(BT->Right);
}
- 后序遍历
void PreorderPrintLeaves( BinTree BT ){
if(!BT)
return;
PreorderPrintLeaves(BT->Left);
printf(" %c", BT->Data);
PreorderPrintLeaves(BT->Right);
}
- 层次遍历
void LevelOrder(BTree T)
{
queue<BTree> Q;
BTree bt;
int flag = 0;
if (T != NULL)
Q.push(T);
while (!Q.empty())
{
bt = Q.front();
Q.pop();
if (flag)
cout << " " << bt->data;
else
{
cout << bt->data;
flag = 1;
}
if (bt->lchild) Q.push(bt->lchild);
if (bt->rchild) Q.push(bt->rchild);
}
}
1.1.4 线索二叉树
- 1.存储结构:
typedef struct node
{ ElemType data; //结点数据域
int ltag,rtag; //增加的线索标记
struct node *lchild; //左孩子或线索指针
struct node *rchild; //右孩子或线索指针
} TBTNode; //线索树结点类型定义
- 2.中序线索二叉树特点?如何在中序线索二叉树查找前驱和后继?
- 中序线索二叉树好处在于遍历二叉树不需要递归,所有节点只需遍历一次!!
- 优点:中序遍历算法既没有递归也没有用栈,所有节点只需遍历一次,空间效率得到提高。
- 结点的后继:
- 结点有右孩子,则为右子树最左孩子节点
- 结点无右孩子,则为后继线索指针指向节点
- 节点的前驱
- 结点有左孩子,则为左子树最右那个孩子
- 结点无左孩子,则为前驱线索指针指向节点
1.1.5 二叉树的应用--表达式树
介绍表达式树如何构造
定义一个树栈s存放数字
定义一个字符栈op存放运算符
while (str[i])
if数字
生成一个树节点存放数字,然后入栈
else运算符
switch 比较优先级
case '<':入栈;break;
case '=':出栈;break;
case '>':
T = new BiTNode;
T->data = op.top();
T->rchild = s.top();
s.pop();
T->lchild = s.top();
s.pop();
s.push(T);
op.pop();
break;
end while
while (op.top() != '#')
T = new BiTNode;
T->data = op.top();
op.pop();
T->rchild = s.top();
s.pop();
T->lchild = s.top();
s.pop();
s.push(T);
endwhile
如何计算表达式树
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;
case '-':
return a - b;
case '*':
return a * b;
case '/':
if (b == 0)
{
cout << "divide 0 error!" << endl;
exit(0);
}
return a / b;
}
}
}
1.2 多叉树结构
1.2.1 多叉树结构
孩子兄弟链结构体定义:
typedef struct node
{
string data;
bool isDie;
struct node* firstchild, * brother;
}DirTNode,*DirTree;
1.3 哈夫曼树
1.3.1 哈夫曼树定义
- 1.什么是哈夫曼树?
在N个带权叶子节点构成的所有二叉树中,带权路径长度WPL最小的二叉树,称为哈夫曼树,或最优二叉树。 - 2.哈夫曼树解决什么问题?
哈夫曼树可用于构造使电文编码的代码长度最短的编码方案。例如:在数据通信中,经常需要将传送的文
字转换为二进制字符0和1组成的二进制字符串,称这个过程为编码。显然,我们希望电文编码的代码长度最短。
1.3.2 哈夫曼树的结构体
typedef struct
{ char data; //节点值
float weight; //权重
int parent; //双亲节点
int lchild; //左孩子节点
int rchild; //右孩子节点
} HTNode;
1.3.2 哈夫曼树构建及哈夫曼编码
- 哈夫曼编码。
void CreateHCode(HCode hcd[], HTNode ht[], int n0)
{
int i, f, c;
HCode hc;
for (i = 0; i < n0; i++)
{
hc.start = n0;
f = ht[i].parent;
c = i;
while (f != -1)
{
if (ht[f].lchild == c)
hc.cd[hc.start--] = '0';
else
hc.cd[hc.start--] = '1';
c = f;
f = ht[f].parent;
}
hc.start++;
hcd[i] = hc;
}
}
1.4 并查集
-
什么是并查集?
并查集支持查找一个元素所属的集合以及两个元素各自所属的集合的合并等运算。当给出两个元素的一个无序对(a ,b)时,
需要快速“合并”a和 b分别所在的集合,这期间需要反复“查找”某元素所在的集合。“并”“查”和“集”3个字由此而来。在这
种数据类型中, n个不同的元素被分为若干组。每组是一个集合,这种集合叫分离集合,称之为并查集 -
并查集解决什么问题,优势在哪里?
并查集:(union-find sets)是一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判
断元素所在集合的操作,应用很多,如其求无向图的连通分量个数、最小公共祖先、带限制的作业排序,还有最完美的
应用:实现Kruskar算法求最小生成树。 -
并查集的结构体、查找、合并操作如何实现?
- 结构体
typedef struct node
{ int data;//结点对应人的编号
int rank; //结点秩:子树的高度,合并用
int parent; //结点对应双亲下标
} UFSTree;//并查集树的结点类型
- 查找
int FIND_SET(UFSTree t[], int 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.谈谈你对树的认识及学习体会。
树的学习学的很懵逼,特别是一开始递归部分不好理解,而且这章节需要记住的知识点很多,孩子节点、兄弟节点、满二叉树、完全二叉树等等一堆东西,我觉得我脑子已经不够用了,PTA上的题目虽然简单,但还是不怎么会,特别是表达式、目录树还有一堆的编程题,还有需要递归的地方。
2.PTA实验作业(4分)
2.1 二叉树
2.1.1 解题思路及伪代码
解题思路:正常建树,然后利用wpl和h来记录树的权值和高度,再然后利用递归来计算带权路径长度和
伪代码:
main
{
BTree bt;
int h,wpl;
建树
GetWPL
输出wpl;
}
wpl
{
if 孩子节点不空
wpl = wpl + (bt->data - '0') * h;
endif
GetWPL(bt->lchild, h + 1, wpl);
GetWPL(bt->rchild, h + 1, wpl);
}
2.1.2 总结解题所用的知识点
树结构体的定义,建树的方法,函数,递归的使用
2.2 目录树
2.2.1 解题思路及伪代码
for 遍历每一个字符
如果当前字符不是字符就将他存在str中
如果是就遍历树结构数组
找str中存的字符串是否已经存在
如果不存在就创建目录
存在就创建文件
endfor
输出
2.2.2 总结解题所用的知识点
sort函数,结构体定义,函数传参,指针,string的使用
3.阅读代码(0--1分)
3.1 题目及解题代码
题目:
class Solution {
public:
vector<vector<int>> ret;
vector<int> path;
void dfs(TreeNode* root, int sum) {
if (root == nullptr) {
return;
}
path.emplace_back(root->val);
sum -= root->val;
if (root->left == nullptr && root->right == nullptr && sum == 0) {
ret.emplace_back(path);
}
dfs(root->left, sum);
dfs(root->right, sum);
path.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
dfs(root, sum);
return ret;
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/path-sum-ii/solution/lu-jing-zong-he-ii-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.2 该题的设计思路及伪代码
-
核心思想是对树进行一次遍历,在遍历时记录从根节点到当前节点的路径和,以防止重复计算。
-
思路:我们可以采用深度优先搜索的方式,枚举每一条从根节点到叶子节点的路径。当我们遍历
到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。 -
时间复杂度:O(N^2),其中 NN 是树的节点数。在最坏情况下,树的上半部分为链状,下半部分为完
全二叉树,并且从根节点到每一个叶子节点的路径都符合题目要求。此时,路径的数目为 O(N),并
且每一条路径的节点个数也为 O(N),因此要将这些路径全部添加进答案中,时间复杂度为 O(N^2))。 -
空间复杂度:O(N),其中 N是树的节点数。空间复杂度主要取决于栈空间的开销,栈中的元素个数不会超过树的节点数。
-
伪代码
用vector定义一个path记录树的路径
用vector<vector<int>>定义一个ret记录多条树的路径
void dfs(TreeNode* root, int sum)
{
如果树节点为空结束
用path.emplace_back记录当前节点元素
sum减当前元素
如果当前节点为叶子节点且sum==0,用ret记录当前路径
dfs(root->left, sum);
dfs(root->left, sum);
用path.pop_back删除最后一个元素
}
3.3 分析该题目解题优势及难点。
- 难点:如何遍历树及记录所需的树的路径。
- 优势:用vector容器灵活的记录数的路径。