DS博客作业03--树
这个作业属于哪个班级 | 数据结构--网络2011/2012 |
---|---|
这个作业的地址 | DS博客作业03--树 |
这个作业的目标 | 学习树结构设计及运算操作 |
姓名 | 王博 |
0.PTA得分截图
树
题目集总得分,请截图,截图中必须有自己名字。题目至少完成2/3,否则本次作业最高分5分。
1.本周学习总结(5分)
学习总结,请结合树的图形展开分析。
1.1 二叉树结构
-
定义:二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。
-
特性:
1.深度为h的二叉树最多有2^h-1个结点。
2.在二叉树的第i层最多有2^(i-1)个结点。
3.具有n个结点的完全二叉树的深度为[log(2,n)]min+1
1.1.1 二叉树的2种存储结构
树的顺序存储和链式存储结构,并分析优缺点。
1.1.1.1 二叉树的顺序储存结构
-
二叉树的顺序存储,就是用一组连续的存储单元存放二叉树中的结点。因此,必须把二叉树的所有结点安排成为一个恰当的序列,结点在这个序列中的相互位置能反映出结点之间的逻辑关系,用编号的方法从树根起,自上层至下层,每层自左至右地给所有结点编号,缺点是有可能对存储空间造成极大的浪费,在最坏的情况下,一个深度为k且只有k个结点的右单支树需要2k-1个结点存储空间。依据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一地反映出结点之间的逻辑关系,这样既能够最大可能地节省存储空间,又可以利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系。
-
完全二叉树
- 普通二叉树
1.1.1.2 二叉树的链式储存结构
- 二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。其结点结构为:
其中,data域存放某结点的数据信息;lchild与rchild分别存放指向左孩子和右孩子的指针,当左孩子或右孩子不存在时,相应指针域值为空(用符号∧或NULL表示)。利用这样的结点结构表示的二叉树的链式存储结构被称为二叉链表
- 结构体定义:
struct TNode {
ElementType Data;
BinTree Left;
BinTree Right;
};
1.1.2 二叉树的构造
总结二叉树的几种构造方法。分析你对这些构造方法的看法。务必介绍如何通过先序遍历序列和中序遍历序列、后序遍历序列和中序遍历序列构造二叉树。
- 先序
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.3 二叉树的遍历
总结二叉树的4种遍历方式,如何实现。
- 先序遍历
void PreorderPrintLeaves(BinTree BT)
{
if (BT != NULL)
{
cout<<BT->Data<<" ";
PreorderPrintLeaves(BT->Left);
PreorderPrintLeaves(BT->Right);
}
}
- 中序遍历
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 线索二叉树
-
线索二叉树如何设计?
利用原来的空链域存放指针,指向树中其他结点。这种指针称为线索。
则ptr指向二叉链表中的一个结点,以下是建立线索的规则:
(1)如果ptr->lchild为空,则存放指向中序遍历序列中该结点的前驱结点。这个结点称为ptr的中序前驱;
(2)如果ptr->rchild为空,则存放指向中序遍历序列中该结点的后继结点。这个结点称为ptr的中序后继;
在决定lchild是指向左孩子还是前驱,rchild是指向右孩子还是后继,需要一个区分标志的。因此,我们在每个结点再增设两个标志域ltag和rtag,注意ltag和rtag只是区分0或1数字的布尔型变量,其占用内存空间要小于像lchild和rchild的指针变量,如图:
typedef char TElemType;
typedef struct BiThrNode
{
TElemType data;
struct BiThrNode* lchild, * rchild;
int ltag, rtag;
}BiThrNode,* BiThrTree;
-
中序线索二叉树特点?如何在中序线索二叉树查找前驱和后继?
中序线索二叉树
为避免悬空,应增设头结点
头结点左孩子指向根结点,右孩子为线索指向最后一个孩子,遍历序列第一个结点前驱为头结点,最后一个结点后继为头结点void InThreading(BiThrTree p) { if (p) { InThreading(p->lchild); if (!p->lchild) { p->ltag = 1; p->lchild = pre; } if (!pre->rchild) { pre->rtag = 1; pre->rchild = p; } pre = p; InThreading(p->rchild); } }
1.1.5 二叉树的应用--表达式树
-
介绍表达式树如何构造
void InitExpTree(BTree &T, string str) //建表达式的二叉树 { stack<BTree> s; //存放结点的栈 stack<char> op; //存放操作符 op.push('#'); //以#结束 while(str[i]!='\0') //遍历数组str { if(!In(str[i]) ) //操作数; { 建立树结点,并让树进栈; } else { 先判断操作栈是否空 再比较数组和栈顶的大小关系 根据大小关系做相应的操作 } } while(op.top() !='#') { 遍历栈 根据关系,找左孩子和右孩子,建树 } }
-
如何计算表达式树
double EvaluateExTree(BTree T)//计算表达式树 { if (!T->lchild && !T->rchild) return T->data - '0'; value1 = EvaluateExTree(T->lchild); //递归口,让式子从叶子结点开始计算; value2 = EvaluateExTree(T->rchild); switch (T->data) 遍历树 { case '+': case '-': case '*': 做相应的计算,并返回式子的结果 case '/': } }
1.2 多叉树结构
1.2.1 多叉树结构
主要介绍孩子兄弟链结构
多叉树是指一个父节点可以有多个子节点,但是一个子节点依旧遵循一个父节点定律,通常情况下,二叉树的实际应用高度太高,可以通过多叉树来简化对数据关系的描述
儿子兄弟表示任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,设置两个指针分别指向该结点的第一个孩子和此结点的右兄弟
typedef struct CSNode
{
TElemType data;
struct CSNode* firstchild, * rightsib;
}CSNode,*CSTree;
1.2.2 多叉树遍历
介绍先序遍历做法
先根遍历(递归,根左右)
后根遍历(递归,左右根)
层次遍历
1.3 哈夫曼树
1.3.1 哈夫曼树定义
什么是哈夫曼树?,哈夫曼树解决什么问题?
当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”。
原则:在构建哈弗曼树时,要使树的带权路径长度最小,所以权重越大的结点离树根越近。
远距离通信的数据传输的最优化
1.3.2 哈夫曼树的结构体
教材是顺序存储结构,也可以自己搜索资料研究哈夫曼的链式结构设计方式。
typedef struct HTreeNode
{
char data[5]; //每个结点是字符类型,最多5个字符
int weight; //字符所占的权重
int parent; //双亲结点所在下标
int left; //左孩子结点所在下标
int right; //右孩子结点所在下标
}HTNode;
1.3.3 哈夫曼树构建及哈夫曼编码
1、根据给定的n个权值{w1,w2,……wn},构造n棵只有根结点的二叉树。F={T1,T2,…,Tn}。
2、在F中选取根结点的权值最小和次小的两棵二叉树作为左、右子树构造一棵新的二叉树,这棵新的二叉树根结点的权值为其
左、右子树根结点权值之和。
3、在集合F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到集合F中。
4、重复(2)、(3)两步,当F中只剩下一棵二叉树时,这棵二叉树便是所要建立的哈夫曼树
- 结合一组叶子节点的数据,介绍如何构造哈夫曼树及哈夫曼编码。
从19,21,2,3,6,7,10,32中选择两个权小结点。选中2,3。同时算出这两个结点的和5。
......
可得出:
则可直接写出编码:
A:11 B:001 C:011 D E:0000 F:0001 G:0100 H:0101 I:1000 J:1001
则:
-
(可选)哈夫曼树代码设计,也可以参考链式设计方法。
#include<iostream> #include<vector> #include<cmath> #include<algorithm> #include<unordered_map> #include<queue> #include<limits> using namespace std; int main() { int n; cin>>n; vector<int>temp(n); for(int i=0;i!=n;++i) cin>>temp[i]; int res=0; for(int i=0;i!=n-1;++i) { sort(temp.begin()+i,temp.end()); temp[i+1]+=temp[i]; res+=temp[i+1]; } cout<<res<<endl; }
1.4 并查集
-
什么是并查集?
并查集,在一些有N个元素的集合)应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
-
并查集解决什么问题,优势在哪里?
并查集在解决连通性问题上有很大优势,比如检查地图上两点是否连通,用并查集可以大大提高查找效率
-
并查集的结构体、查找、合并操作如何实现?
结构体
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; } }
查找
void FIND_SET(UFSTree t[],int x) { if(x!=t[x].parent) return (FIND_SET(t,t[x].parent)); else return x; }
合并
void UNION(UFSTree t[],int x,int y) { x=FIND_SET(t,x); y=FIND_SET(t,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.谈谈你对树的认识及学习体会。
难,开始头疼了,并没有以前学习的激情了;其中递归使用也非常多,对基础理论知识的考察也更多了。
2.PTA实验作业(4分)
此处请放置下面2题代码所在码云地址(markdown插入代码所在的链接)。如何上传VS代码到码云
2.1 二叉树
输出二叉树每层节点
、二叉表达式树
、二叉树叶子结点带权路径长度和
三题自选一题介绍。
2.1.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.2 总结解题所用的知识点
pop,push函数的调用,重温switch-case语句QwQ,函数递归
2.2 目录树
2.2.1 解题思路及伪代码
#include <stdio.h>
#include<stdlib.h>
#include <string.h>
#define True 1
#define False 0
typedef int Bool;
typedef struct node* Node;
struct node {
char*Name;
Bool isCatalog;
Node File;
Node Catalog;
Node Brother;
} Head;
void Print(Node, int);
void Read();
Node New(char*);
Node InsertCatalog(Node, char*);
Node InsertFile(Node, char*);
int main() {
int n;
scanf("%d", &n);
Head.Name = (char*) malloc(sizeof(char) * 5);
strcpy(Head.Name, "root");
Head.File = NULL;
Head.Catalog = NULL;
Head.Brother = NULL;
Head.isCatalog = True;
for (int i = 0; i < n; i++) {
getchar();
Read();
}
Print(&Head, 0);
return 0;
}
void Read() {
char FileName[261];
Node HTemp = &Head;
scanf("%s", FileName);
char words[261];
int j, L = 0;
for (int i = 0; i < strlen(FileName); i++) {
if (FileName[i] == '\\') {
for (j = L; j < i; j++) {
words[j - L] = FileName[j];
}
words[j - L] = '\0';
HTemp->Catalog = InsertCatalog(HTemp->Catalog, words);
HTemp = HTemp->Catalog;
while (strcmp(HTemp->Name, words))
HTemp = HTemp->Brother;
L = i + 1;
}
}
if (L < strlen(FileName)) {
for (j = L; j <= strlen(FileName); j++) {
words[j - L] = FileName[j];
}
HTemp->File = InsertFile(HTemp->File, words);
}
}
Node InsertCatalog(Node Catalog, char*InsertCatalogName) {
if (!Catalog || strcmp(Catalog->Name, InsertCatalogName) > 0) {
Node temp = New(InsertCatalogName);
temp->Brother = Catalog;
return temp;
}
if (strcmp(Catalog->Name, InsertCatalogName) == 0)
return Catalog;
Catalog->Brother = InsertCatalog(Catalog->Brother, InsertCatalogName);
return Catalog;
}
Node InsertFile(Node File, char*InsertFileName) {
if (!File || strcmp(File->Name, InsertFileName) > 0) {
Node Insert = New(InsertFileName);
Insert->isCatalog = False;
Insert->Brother = File;
return Insert;
}
File->Brother = InsertFile(File->Brother, InsertFileName);
return File;
}
Node New(char*Name) {
Node newNode = (Node) malloc(sizeof(struct node));
newNode->Name = (char*) malloc(sizeof(char) * (strlen(Name) + 1));
strcpy(newNode->Name, Name);
newNode->Brother = NULL;
newNode->File = NULL;
newNode->Catalog = NULL;
newNode->isCatalog = True;
return newNode;
}
void Print(Node H, int Space) {
if (H) {
for (int i = 0; i < Space; i++)
printf(" ");
printf("%s\n", H->Name);
if (H->isCatalog == 1)
Print(H->Catalog, Space + 2);
Print(H->File, Space + 2);
Print(H->Brother, Space);
}
}
2.2.2 总结解题所用的知识点
各种函数。。。。
3.阅读代码(0--1分)
找1
份优秀代码,理解代码功能,并讲出你所选代码优点及可以学习地方。主要找以下类型代码:
- 考研题
- 蓝桥杯题解,这个连接只是参考的题目,具体可以自己搜索蓝桥杯,查看历年的题解。只能找树相关题目介绍。
- leecode--树
注意:不能选教师布置在PTA的题目。完成内容如下。
3.1 题目及解题代码
可截图,或复制代码,需要用代码符号渲染。
3.2 该题的设计思路及伪代码
请用图形方式展示解决方法。同时分析该题的算法时间复杂度和空间复杂度
。
先遍历输出记录树的遍历顺序为空则输出#,再比较两端字符串是否相同,最后对数据进行处理输出