DS博客作业03--树
| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
|---|---|
| 这个作业的地址 | DS博客作业03--数 |
| 这个作业的目标 | 学习树结构设计及运算操作 |
| 姓名 | 王历 |
0.PTA得分截图

1.本周学习总结(5分)
1.1 二叉树结构
- 二叉树:是一个有限的结点集合,这个集合或者为空,或者由一个根结点和两棵互不相交的称为左子树和右子树的二叉树组成。
- 注:二叉树中不存在度大于2的结点。
![]()
- 二叉树的五种基本形态:

- 两种特殊的二叉树:
- 满二叉树:在一棵二叉树中,如果所有分支结点都有双分结点,并且叶结点都集中在二叉树的最下一层,即为满二叉树。高度为H的满二叉树恰好有(2^H-1)个结点。
![]()
- 完全二叉树:深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应,没有单独右分支结点。完全二叉树实际上是对应的满二叉树删除叶结点层最右边若干个结点得到的。
![]()
- 两者的区别:满二叉树是叶子一个也不少的树,而完全二叉树虽然前n-1层是满的,但最底层却允许在右边缺少连续若干个结点。满二叉树是完全二叉树的一个特例。
1.1.1 二叉树的2种存储结构
- 二叉树的顺序存储结构:用一组地址连续的存储单元来存放二叉树的数据元素,因此必须确定好树中各数据元素的存放次序,使得各数据元素在这个存放次序中的相互位置能反映出数据元素之间的逻辑关系。
![]()
![]()
- 二叉树顺序存储结构的优缺点:
- 对于完全二叉树来说,其顺序存储是非常合适的。
- 对于普通二叉树来说:在最坏的情况下,一个深度为k且只有k个结点的单支树(树中不存在度为2的结点)
却需要 2^k-1 的一维数组。空间利用率太低! - 数组,查找、插入删除不方便。
- 二叉树的链式存储结构:用一个链表来存储一棵二叉树,二叉树中的每一个结点用链表中的一个结点来存储。
![]()
1.1.2 二叉树的构造
- 构造方法:
(1). 顺序存储结构转二叉链
(2). 先序遍历建二叉树
(3). 层次遍历法建二叉树--队列
(4). 括号法建二叉树--栈
(5). 先序+中序序列造二叉树
(6) . 中序+后序序列造二叉树
- 根据先序序列和后序序列构造二叉树:
![]()
![]()
- 根据后序序列和中序序列构造二叉树:
![]()
![]()
1.1.3 二叉树的遍历
- 概念:二叉树的遍历是指按照一定次序访问树中所有节点,并且每个节点仅被访问一次的过程。它是最基本的运算,是二叉树中所有其他运算的基础。
二叉树的组成:
![]()
1.先序遍历过程:NLR- 访问根节点;
- 先序遍历左子树;
- 先序遍历右子树。
![]()
递归算法:
void PreOrder(BTree bt)
{
if(bt != NULL)
{
printf("%c",bt->data);//访问根结点
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
- 中序遍历过程:LNR
- 中序遍历左子树;
- 访问根节点;
- 中序遍历右子树。
![]()
递归算法:
void InOrder(BTree bt)
{
if(bt != NULL)
{
InOrder(bt->lchild);
printf("%c",bt->data);//访问根节点
InOrder(bt->rchild);
}
}
- 后序遍历过程:LRN
- 后序遍历左子树;
- 后序遍历右子树;
- 访问根节点。
![]()
递归算法:
void PostOrder(BTree bt)
{
if(bt != NULL)
{
PostOrder(bt->lchild);
PostOrder(bt->rchild);
PostOrder("%c",bt->data);//访问根节点
}
}
- 层次遍历:在进行层次遍历时,对某一层的节点访问完后,再按照它们的访问次序对各个节点的左、右孩子顺序访问。其过程是:
初始化队列,先将根节点进队。
while(队列不空)
{
队列中出列一个节点*p,访问它;
若它有左孩子节点,将左孩子节点进队;
若它有右孩子,将右孩子进队。
}
对应算法如下:
void AllPath2(BTree b)
{
int k;
BTree p;
QuType *qu;//定义非环形队列指针
InitQueue(qu);//初始化队列
b->parent = -1;//创建根节点对应的队列元素
enQueue(qu, b);//根节点进队
while(!QueueEmpty(qu))
{
deQueue(qu, p);//出队
if(p->lchild == NULL && p->rchild == NULL)
{
k = qu->front;//输出节点p到根节点的路径逆序列
while(qu->data[k]->parent != -1)
{
printf("%c->",qu->data[k]->data);
k = qu->data[k]->parent;
}
printf("%c\n",qu->data[k]->data);
}
if(p->lchild !=NULL)//节点p有左孩子
{
p->lchild->parent = qu->front;//其双亲位置为qu->front
enQueue(qu,p->lchild)://节点p的左孩子进队
}
if(p->rchild != NULL)//节点p有右孩子
{
p->rchild->parent = qu->front;
enQueue(qu,p->rchild);//节点p的右孩子进队
}
}
}
1.1.4 线索二叉树
- 二叉链存储结构时,每个节点总有两个指针域,总共有2n个指针域。
- 有效指针域:n-1(根节点没指针指向)
- 空指针域:n+1
- 利用这些空指针域,指向该线性序列中的“前驱”和“后继”的指针,称作线索。
![]()
先序线索二叉树:
![]()
后序线索二叉树:
![]()
中序线索二叉树:


中序线索二叉树创建
- CreatThread(b)算法:对以二叉链存储的二叉树b进行中序线索化,并返回线索化后头结点的指针root。
TBTNode *pre;//全局变量
TBTNode *CreatThread(TBTNode *b)//中序线索化二叉树
{
TBTNode *root;
root = (TBTNode *)malloc(sizeof(TBTNode));//创建头结点
root->ltag = 0;root->rtag = 1;root->rchild = b;
if(b == NULL)root->lchild = root;//空二叉树
else
{
root->lchild = b;
pre = root;//pre是*p的前驱结点,供加线索用
Thread(b);
pre->rchild = root;//中序遍历线索化二叉树
pre->rtag = 1;
root->rchild = pre;//头结点右线索化
}
return root;
}
- Thread(p)算法:对以*p为根结点的二叉树子树的中序线索化。
void Thread(TBTNode *&p)//对二叉树b进行中序线索化
{
if(p != NULL)
{
Thread(p->lchild);
if(p->lchild == NULL)
{
p->lchild = pre;p->ltag = 1;
}
else p->ltag = 0;
if(pre->rchild == NULL)
{
pre->rchild = p;pre->rtag = 1;
}
else pre->rtag = 0'
pre = p;
Thread(p->rchild);
}
}
1.1.5 二叉树的应用--表达式树

伪代码:
while(遍历表达式)
{
若为操作数,生成树结点,入树根栈
若为运算符:
若优先级 > 栈顶运算符,则入运算符栈
若小于,出栈,树根栈弹出2个结点建树,新生成树根入树根栈
若相等,则出栈运算符栈
}



1.2 多叉树结构
1.2.1 多叉树结构
- 顺序存储 - 双亲存储结构:
![]()
2.孩子链存储结构:
![]()
3.孩子兄弟链存储结构:孩子兄弟链存储结构是为每个结点设计3个域:
- 一个数据元素域
- 第一个孩子结点指针域
- 一个兄弟结点指针域
类型声明
typedef struct tnode
{
ElemType data;//结点的值
struct tnode *son;//指向兄弟
struct tnode *brother;2//指向孩子结点
}TSBNode;

1.2.2 多叉树遍历
树的遍历
树的遍历运算是指按某种方式访问树中的每一个结点并且一个结点只被访问一次。
主要的遍历方法:
-
先根遍历:若树不空,则先访问根结点,然后依次先根遍历各棵子树。
![]()
-
后根遍历:若树不空,则先依次后根遍历各棵子树,然后访问根结点。
-
层次遍历:若树不空,则自上而下、自左至右访问树中每个结点。
1.3 哈夫曼树
1.3.1 哈夫曼树定义
- 设二叉树具有n个带权值的叶子结点,那么从根结点到各个叶子结点的路径长度与相应结点的权值的乘积的和,叫做二叉树的带权路径长度。
![]()
- 具有最小带权路径长度的二叉树称为哈夫曼树。
![]()
1.3.2 哈夫曼树的结构体
typedf struct
{
char data;//结点值
float weight;//权重
int parent;//双亲结点
int lchild;//左孩子结点
int rchild;//右孩子结点
}HTNode;
1.3.2 哈夫曼树构建及哈夫曼编码
构造哈夫曼树的原则:
- 权值越大的叶结点越靠近根结点。
- 权值越小的叶结点越远离根结点。
构造哈夫曼树的过程:
1.根据给定的n个权值{w1,w2,……wn},构造n棵只有根结点的二叉树。F={T1,T2,…,Tn}。
2.在F中选取根结点的权值最小和次小的两棵二叉树作为左、右子树构造一棵新的二叉树,这棵新的二叉树根结点的权值为其左、右子树根结点权值之和。
3.在集合F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到集合F中。
4.重复(2)、(3)两步,当F中只剩下一棵二叉树时,这棵二叉树便是所要建立的哈夫曼树。
哈夫曼编码:根结点到叶子结点经过路径组成的0,1序列。
![]()
1.4 并查集
并查集:支持查找一个元素所属的集合以及两个元素各自所属的集合的合并等运算。当给出两个元素的一个无序对(a,b)时,需要快速“合并”a和b分别所在的集合,这期间需要反复“查找”某元素所在的集合。“并”、“查”和“集”3个字由此而来。在这种数据类型中,n个不同的元素被分成若干组。每一组是一个集合,这种集合叫分离集合,称之为并查集。
[并查集](https://baike.baidu.com/item/并查集/9388442 )
并查集的初始化
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);//递归在双亲中找x
else
return(x);//双亲是自己,返回x
}
两个元素各自所属集合的合并
void UNION(UFSTree t[],int x,int y)//将x和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.谈谈你对树的认识及学习体会。
树 是一种 数据结构 ,它是由 n (n>=1 )个有限节点组成一个具有层次关系的 集合 。 把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 它具有以下的特点: 每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树。
(1). 每个元素称为节点( node )。
(2). 有一个特定的节点被称为根节点或树根( root )。
(3). 除根节点之外的其余数据元素被分为 个互不相交的集合 ,其中每一个集合 本身也是一棵树,被称作原树的子树。
树也可以这样定义:树是由根节点和若干颗子树构成的。 树是由一个集合以及在该集合上定义的一种关系构成的。
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 解题思路及伪代码
- 解题思路
要用左孩子右兄弟的二叉链表存储
注意同层目录排在文件前,同类按字典顺序输出
伪代码:
对结构体进行定义;
初始化树,建立根节点root;
输入字符串,进行建树;
void CreatTree(Tree& bt, string str, int i)//建树,
{
新建结点temp,ptr;初始化结点;
切割字符串;新节点name为该段字符;
if 该段字符串为目录,isfile改为false;
if (temp为文件)
InitFile(temp, bt);//插入文件
else //temp为目录
InitList(temp, bt);//插入目录
CreatTree(temp, str, i);
}
void InitList(Tree& temp, Tree& bt)//插入目录
{
定义结构体指针btr来遍历二叉树bt;
btr = bt->child;//btr先指向bt的孩子;
/*先对第一个兄弟结点进行判断*/
if (没有第一个孩子|| btr为文件 || 第一个孩子字典序大于该结点)//可插入
进行插入temp->brother = btr;bt->child = temp;//修改孩子指针
else if (二者相等)
直接使temp指向btr;
else //查找兄弟节点
while (btr->brother != NULL)
if (兄弟节点为文件 || 兄弟节点字典序大于该节点)
找到可插入位置,break;
else if (二者相等)
直接使temp指向btr->brother;break;
else
btr = btr->brother;//遍历下一兄弟结点;
end if
end while
if (btr->brother为空 || btr->brother->name != temp->name)
进行插入操作:temp->brother = btr->brother;btr->brother = temp;
end if
end if
}
void InitFile(Tree& temp, Tree& bt)//对文件temp找一个可插入位置
{
定义结构体指针btr来遍历二叉树bt;
btr = bt->child;//btr先指向bt的孩子;
if (第一个孩子为空 || btr为文件 && 结点字典序大于等于该节点)
进行插入,修改bt的孩子指针;
else //判断兄弟结点
while (btr->brother != NULL)
if (btr->brother为文件 && 兄弟节点字典序大于该节点)
找到可插入位置,break;
else
btr = btr->brother;//遍历下一个兄弟结点
end if
end while
对temp进行插入操作:temp->brother = btr->brother;btr->brother = temp;
end if
}
2.2.2 总结解题所用的知识点
- 创建孩子兄弟链
- 后序遍历目录树,排序每个结点的子目录和子文件
- 先序遍历进行输出

























浙公网安备 33010602011771号