这个作业属于哪个班级 | 数据结构--网络2011/2012 |
---|---|
这个作业的地址 | DS博客作业03--树 |
这个作业的目标 | 学习树结构设计及运算操作 |
姓名 | 骆念念 |
0.PTA得分截图
树题目集总得分,请截图,截图中必须有自己名字。题目至少完成2/3,否则本次作业最高分5分。
1.本周学习总结(5分)
学习总结,请结合树的图形展开分析。
1.1 二叉树结构
二叉树的结构体定义
typedef struct node
{
ElemType data;
struct node *lchild,*rchild;
}BTNode,*BTree;
二叉树是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树 。
二叉树的基本五种形态
1.1.1 二叉树的2种存储结构
树的顺序存储和链式存储结构,并分析优缺点。
二叉树的顺序存储结构是指用顺序表(数组)来存储二叉树,它指适用于完全二叉树
二叉树的顺序存储结构
BTree CreateBTree(string str,int i)
{
int len;
BTree bt;
bt=new BTNode;
len=str.size();
if(i>len||i<=0)
return NULL;
if(str[i]=='#')
return NULL;
bt->data=str[i];
bt->lchild=CreateBTree(str,2*i);//注意,如果i是从0开始的,那么2*i改为2*i+1,2*i+1改为2*i+2
bt->rchild=CreateBTree(str,2*i+1);
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,++i);
bt->rchild=CreateTree(str,++i);
return bt;
//注意,一定要在函数的形参中的i前面加上&,因为这里的i是一直在变。
顺序存储和链式存储的优缺点
1.顺序存储时,相邻数据元素的存放地址也相邻(逻辑与物理统一);要求内存中可用存储单元的地址必须是连续的。
(1)优点:存储密度大,存储空间利用率高。
(2)缺点:插入或删除元素时不方便。
2.链式存储时,相邻数据元素可随意存放,但所占存储空间分两部分,一部分存放结点值,另一部分存放表示结点间关系的指针
(1)优点:插入或删除元素时很方便,使用灵活。
(2)缺点:存储密度小,存储空间利用率低。
1.1.2 二叉树的构造
总结二叉树的几种构造方法。分析你对这些构造方法的看法。务必介绍如何通过先序遍历序列和中序遍历序列、后序遍历序列和中序遍历序列构造二叉树。
二叉树的构造有:
**
1.顺序存储结构转二叉链
2.先序遍历建二叉树
3.层次遍历建二叉树-队列
4.括号法建二叉树-栈
5.先序+中序建二叉树
6.中序+后序建二叉树
**
先序和中序建二叉树
BTree CreateBT(char *pre,char *in,int n)
{
//先序:ABDFCEGH
//中序:BFDAGEHC
if n<=0,返回空。递归结束//输入错误
创建根节点BT
BT->data=*pre;
查找根节点在中序序列位置k
创建左子树
BT->lchild=CreateBT(pre+1,in.k)
创建右子树
BT->rchild=CreateBT(pre+k+1,in+k+1,n-k-1)
}
BTree CreateBT(char *pre,char *in,int n)
{
BTNode *s;
char *p;
int k;
if(n<=0)
return NULL;
s=new BTNode;
s->data=*pre;
for(p=in;p<in+n;p++)
if(*p=*pre)
break;
k=p-in;
s->lchild=CreateBT(pre+1,in,k)
s-rchild=CreateBT(pre+k+1,p+1,n-k-1)
return s;
}
中序和后序还原二叉树
BTree CreateBT(char *post,char *in,int n)
{
BTNode *s;
char *p;
int k;
if(n<=0)
return NULL;
s=new BTNode;
s->data=*(post+n-1);
for(p=in;p<in+n;p++)
if(*p==*(post+n-1))
break;
k=p-in;
s->lchild=CreateBT(post,in,k);
s->rchild=CreateBT(post+k+1,p+1,n-k-1);
return s;
}
1.1.3 二叉树的遍历
总结二叉树的4种遍历方式,如何实现。
二叉树的遍历有先序遍历,中序遍历,后序遍历,层次遍历
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;
}
}
void LevelOrder(BTree& b)//层次遍历
{
BTree p;
queue<BTree>qu;
int flag = 0;
qu.push(b);
if (b != NULL)
{
while (!qu.empty())//队列不为空时
{
p = qu.front();
qu.pop();
if (!flag && p->data != NULL)
{
cout << p->data;
flag = 1;//用于输出空格
}
else if (flag && p->data != NULL)
{
cout << " " << p->data;
}
if (p->lchild != NULL)
{
qu.push(p->lchild);
}
if (p->rchild != NULL)
{
qu.push(p->rchild);
}
}
}
if (!flag)
cout << "NULL";
}
1.1.4 线索二叉树
线索二叉树如何设计?
线索二叉树的定义:二叉链存储结构时,每个节点有两个指针域,总共有2n个指针域,有效指针域为n-1,空指针域为n+1,利用这些空指针域,指向该线性序列中的前驱和后继的指针称为线索。
若结点有左子树,则lchild指向左孩子,否则,lchild指向其直接前驱(即线索)。若结点有右子树,则rchild指向其右孩子,否则,rchild指向其直接后继,用RTag和LTag来标识两个指针域。0表示指向其孩子,1表示指向其前驱和后继。在画线索二叉树时,线索要用虚线表示,最重要的一点,线索要依据遍历来设计。
中序线索二叉树特点?如何在中序线索二叉树查找前驱和后继?
在中序线索二叉树中,任意一个结点后继,若结点有右孩子,则为右子树最左孩子结点,若结点无右孩子,则为后继线索指针指向结点。对于任意一个节点的前驱,节点有左孩子,则为左子树最右那个孩子,若结点无左孩子,则为前驱线索指针指向结点
1.1.5 二叉树的应用--表达式树
介绍表达式树如何构造
建两个栈s和op分别来存放数字和运算符,遇到数字就赋值给树节点T然后入栈s,遇到运算符就先比较运算符的大小,要比较与op栈顶的符号的先后级,小于就直接进op栈,大于则要先把栈顶的运算符与s中的元素的关系建立,然后再将T进s栈,这样建树
如何计算表达式树
伪代码
建立表达式二叉树
定义栈 s 用来存储用运算的数字
定义字符栈 o p 用来存储数字运算符号
首先 将’#‘ 如栈op
当str不为空的时候 进行while
if 是数字
就建立一个树节点,将它赋值为str{i}的值,还要就左右孩子弄成NULL, 还要入栈
else 调动Precede
对它出现的运算符一一进行运作,
用swich
while op的top不是#
创建新的树节点
赋予为op.top的值
在将栈里的后面两个值分别给左右孩子
/*计算表达式树*/
利用递归把所有字符转换成数字
while 若有树节点
就计算
1.2 多叉树结构
1.2.1 多叉树结构
主要介绍孩子兄弟链结构
*孩子兄弟链存储结构是为每个结点设计3个域:
1.一个数据元素域
2.第一个孩子结点指针域
3.一个兄弟结点指针域
*
结构体定义
typedef struct tnode
{
ElemType data;//结点值
struct tnode *son;//指向兄弟
struct tnode *brother;//指向孩子
}TSBNode;
每个结点固定只有两个指针域,找父亲不容易。
1.2.2 多叉树遍历
介绍先序遍历做法
先访问根节点,先序遍历左子树,先序遍历右子树。
1.3 哈夫曼树
1.3.1 哈夫曼树定义
什么是哈夫曼树?,哈夫曼树解决什么问题?
哈夫曼树的定义:设二叉树具有n个带权值的叶子节点,那么从根节点到各个叶子结点的路径长度与相应节点权值的乘积的和,叫做二叉树的带权路径长度。那么具有最小带权路径长度的二叉树称为哈夫曼树。
哈夫曼树可以解决最短带权路径和编码技术。
1.3.2 哈夫曼树的结构体
教材是顺序存储结构,也可以自己搜索资料研究哈夫曼的链式结构设计方式。
//顺序存储
typedef struct
{
char data;
float weight;
int parent;
int lchild;
int rchild;
}HTNode;
typedef struct tagHFtree
{
//链式存储
char data;
double weight;
struct tahHFtree *parent;
struct tagHFtree *lchild;
struct tagHFtree *rchild;
struct tagHFtree *next;
}HFtree;
1.3.3 哈夫曼树构建及哈夫曼编码
结合一组叶子节点的数据,介绍如何构造哈夫曼树及哈夫曼编码。
在给定一组叶子结点的数据中,找到一个最小的数和次小的数,将两个数相加,得到根节点的值,并且将这两个数作为该根的叶子结点,然后在这组数据中删除这两个数,把这两个数的和加入到这一组数据中。
eg:
哈夫曼编码:从根节点开始,左边为0,右边为1,依次进行下去
(可选)哈夫曼树代码设计,也可以参考链式设计方法。
1.4 并查集
什么是并查集?
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。
并查集解决什么问题,优势在哪里?
可以应用于亲戚关系和朋友圈应用。能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数、最小公共祖先、带限制的作业排序,还有最完美的应用:实现Kruskar算法求最小生成树。
并查集的结构体、查找、合并操作如何实现?
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;//秩初始化为0
t[i].parent=i;//双亲初始化指向自己
}
}
//查找
int FIND_SET(UFSTree t[],int x)//在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.谈谈你对树的认识及学习体会。
树的结构与我们之前学的不同,它是一种非线性结构,我觉得树结构最典型的就是web前端代码。通过树结构,能够压缩高度,在查找时也比较方便,可以通过树的遍历查找。学习树的过程中,有很多的概念,让我有点混淆,对于树的代码也比较生疏。树的很多时候可以通过递归实现,但是递归非常不太好理解。所以在代码层面上还是很多不懂不会。
2.PTA实验作业(4分)
此处请放置下面2题代码所在码云地址(markdown插入代码所在的链接)。如何上传VS代码到码云
2.1 二叉树
输出二叉树每层节点、二叉表达式树、二叉树叶子结点带权路径长度和 三题自选一题介绍。
输出二叉树每层节点
2.1.1 解题思路及伪代码
//层次遍历
BTree p
queue<BTree>qu用队列来存放每层结点
先把头结点入队
判断队列是否为空
不为空则取对头元素
出队
判断头结点的左右孩子是否为空
不为空
左孩子入队
右孩子入队
这样每次取一个结点后,都将它的孩子入队
实现层次遍历
2.1.2 总结解题所用的知识点
1.利用flag来控制输出要求的空格隔开
2.利用队列来实现层次遍历
3.c++的queue模板
2.2 目录树
2.2.1 解题思路及伪代码
#include<iostream>
#include<string>
#include<stack>
#include<vector>
#include<queue>
using namespace std;
typedef struct node { //二叉树的二叉链表存储表示
string data;
bool isDir;
struct node* firstchild, * brother;
}DirTNode, * DirTree;
void InitDirTree(DirTree& tr);
void CreateDirTree(DirTree& tr);
void InsertTreeNode(DirTree& tr, DirTree node);
void PrintDirTree(DirTree tr, int h);
int main()
{
DirTree tr;
CreateDirTree( tr);
PrintDirTree(tr, 1);
}
void InitDirTree(DirTree& tr)
{
tr = new DirTNode;
tr->data = 'root';
tr->isDir = true;
tr->firstchild = NULL;
tr->brother = NULL;
}
void CreateDirTree(DirTree& tr)
{
int num;
int i;
string nodestr;
int begin;
int index;
string datastr;
DirTree trnode;
DirTree rootNode;
InitDirTree(tr);
//分离字符串,生成树节点
rootNode = tr;
cin >> num;
for (i = 1; i <= num; i++)
{
cin >> nodestr;
begin = 0;
while ((index = nodestr.find('\', begin)) != -1)
{
datastr = nodestr.substr(begin, index - begin);
begin = index + 1;
//生成树节点并插入目录树
trnode = new DirTNode;
trnode->data = datastr;
trnode->isDir = true;
trnode->firstchild = NULL;
trnode->brother = NULL;
InsertTreeNode(tr, trnode);
}
if (begin < nodestr.length())
{
datastr = nodestr.substr(begin, nodestr.length() - begin);
//生成文件结点,并插入目录树
//生成树节点并插入目录树
trnode = new DirTNode;
trnode->data = datastr;
trnode->isDir = false;
trnode->firstchild = NULL;
trnode->brother = NULL;
InsertTreeNode(tr, trnode);
}
}
}
//树节点插入目录树
void InsertTreeNode(DirTree& tr, DirTree node)
{
DirTree head;
head = tr->firstchild;
//没有孩子,直接生成第一个孩子结点,建立firstchild
if (tr->firstchild == NULL)
{
tr->firstchild = node;
tr = node;
return;
}
//数据相等且目录文件属性相等,则不断建孩子结点,当前指针
if (tr->firstchild->data == node->data &&
tr->firstchild->isDir == node->isDir)
{
tr = tr->firstchild;//更新tr
return;
}
//更改孩子
if ((tr->firstchild->isDir == false && node->isDir == true) && (node->isDir == tr->firstchild->isDir && node->data>tr->data))
{
node->brother=tr->firstchild;
tr->firstchild = node;
tr = node;
return;
}
//2插入作为兄弟
//没有兄弟
if (head->brother == NULL)
{
head->brother = node;
tr = node;
return;
}
}
void PrintDirTree(DirTree tr, int h)
{
if (tr == NULL)
return;
for (int i = 0; i < h; i++)
{
cout << " ";
}
cout << tr->data << endl;
PrintDirTree(tr->firstchild, h+1);
PrintDirTree(tr->brother ,h );
}
2.2.2 总结解题所用的知识点
3.阅读代码(0--1分)
找1份优秀代码,理解代码功能,并讲出你所选代码优点及可以学习地方。主要找以下类型代码:
考研题
蓝桥杯题解,这个连接只是参考的题目,具体可以自己搜索蓝桥杯,查看历年的题解。只能找树相关题目介绍。
leecode--树
注意:不能选教师布置在PTA的题目。完成内容如下。
3.1 题目及解题代码
可截图,或复制代码,需要用代码符号渲染。
3.2 该题的设计思路及伪代码
请用图形方式展示解决方法。同时分析该题的算法时间复杂度和空间复杂度。
使用深度优先搜索合并两个二叉树,从根节点开始同时遍历两个二叉树,并将对应的节点进行合并。
两个二叉树的对应节点可能存在以下三种情况:
1.如果两个二叉树的对应节点都为空,则合并后的二叉树节点也为空
2.如果两个二叉树的对应节点只有一个为空,则合并后的二叉树的对应节点为其中的非空节点
3.如果两个二叉树的对应节点都不为空,则合并后的二叉树的对应节点的值为两个二叉树的对应节点值之和。
对一个结点进行合并之后,还要对该节点的额左右子树分别进行合并。这是一个递归过程
时间复杂度:O(min(m,n)),其中m和n分别是两个二叉树的节点个数。
空间复杂度:O(min(m,n))
3.3 分析该题目解题优势及难点。
优势是通过该题了解到树的合并,以及递归。难点是如何通过代码实现树的合并。