Author:JaneOnly300
Date:2021:12.6
Categories: 数据结构(专升本)
本章参考王卓数据结构与算法基础
树形结构(非线性结构),结点之间有分支,具有层次关系。

树是有n个结点的有限集合


它是一个递归嵌套的定义.

有序树.子树有次序和无次序
m(>=0)的互不相交的树的集合,一颗树也可以看成一个特殊森林

普通树(多叉树)若不转换为二叉树,那么就会导致运算能难…二叉树的结构性强,规律性强,每个节点最多能有2个分支节点或者叶子,所有的树都可以转换为二叉树,这样就能解决很多树的存储结构和运算的存储性
注意: 二叉树不是树的特殊情况,他们是两种概念。
二叉树节点的子树一定需要区分左右子树!!


虽然二叉树和树的概念不同,但树的基本术语和二叉树通用




二叉树有两种特殊形式,满二叉树和完全二叉树。

在同样深度的二叉树结点个数最多
在同样深度的二叉树叶子结点个数最多
深度为k的具有n个结点的二叉树,每一个结点与深度为k的满二叉树中编号一一对应的就是完全二叉树。




按照满二叉树的结点层此编号,一次存放二叉树当中的元素

#define MAXSIZE 100
Typedef TElemType sqlBiTree[MAXSIZE]
SqBiTree bt;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mq7rgewY-1639276286536)(%E9%98%BF%E6%96%AF%E9%A1%BF#pic_center)]

每个节点有两个指针,分别指向左孩子和右孩子。

typedef struct BiNode{
TElemType data;
struct BiNode *lchild,*rchild;
}BiNode,*BiTree;

结论:
在n个结点当中,一共有n+1个空指针域

遍历就是通过某一条搜索路径访问二叉树的结点,使得每个结点都被访问一次
为了得到树的线性排列
若是规定先左后右边,则有三种情况
DLR:先序遍历
LDR:中序遍历
LRD:后序遍历

练习1:

练习2

算法时间复杂度为On
void preOrder(Btree *T)
{
if(T != NULL) {
printf("%d ", T->element); //访问根节点
preOrder(T->lchild); //先根序遍历左子树
preOrder(T->rchild); //先根序遍历右子树
}
}
void inOrder(Btree *T)
{
if(T != NULL) {
inOrder(T->lchild); //中根序遍历左子树
printf("%d ", T->element); //访问根节点
inOrder(T->rchild); //中根序遍历右子树
}
}
void postOrder(Btree *T)
{
if(T != NULL) {
postOrder(T->lchild); //后根序遍历左子树
postOrder(T->rchild); //后根序遍历右子树
printf("%d ", T->element); //访问根节点
}
}

//创建一个循环队列
typedef struct{
BTNode data[MaxSize]; //存放队中的元素
int front,rear; //队头和尾
}SqQueue;
//层侧遍历算法
void LevelOrder(BTNode *b){
BTNode *p; SqQueue *qu;
initQueue(qu);
enQueue(qu,b);
while(!QueueEmpty(qu)){ //如果队列不为空
DeQueue(qu,p);
printf("%c",p->data);//访问数
if(p->Lchild!=null) enQueue(qu,p->LChild);
if(p->rChild!=null) enQueue(qu,p->rChild);
}
}

算法的实现: (先序遍历)
int Copy(BiTree T,BiTree &NewT){
if(T == null ){ //如果是空树,返回
NewT = NULL ; return 0;
}else{
NewT = new BiTNode;
NewT->data = T->Data;
//
Copy(T->LChild,New->Lchild();
Copy(T->RChild,New->Rchild();
}
}
int Depth(BiTree T){
if(T == null) return 0; //空树返回0
else{
m = Depath(T->lChild); //计算左子树的深度
n = Depath(T->RChild); //计算右子树的深度
if(m>n) return (m+1);
else{
return (n+1)
}
}
}
int CountNode(BiTree b){
if(b == null){
return 0;
}else{ //如果不等于null
//计算左子树+右边子树的的节点+根节点
return CountNode(b->Lchild)+CountNode(B->Rchild)+1;
}
}
int LeadCount(BiTree T){
if(T == null){
return 0;
}
if(T->LChild == null && T->Rchild == null){
return 1; //找出了一个叶子结点
}else{
return LeadCount(T->LChild)+LeadCount(T->Rchild)
}
}
若果二叉树的各个节点都不一样,则而二叉树的先序遍历中序遍历和后序遍历都是唯一的
由二叉树的先序和中序,或者由二叉树的后序和中序则可以确定一个唯一的二叉树
使用二叉链表作为二叉树存储结构时,可以方便找到某个结点左右孩子; 但一般情况, ** 无法直接找到该结点在某种遍历序列中的前驱和后继结点。**
了利用空着的指针域
利用二叉链表的空指针域
如果某个结点的左孩子为空,则将左孩子指针域改为指向前驱,如果右孩子为空,则指向后继。
这种改变指向的指针称之为线索
对于二叉树按照某种遍历次使得其变为线索二叉树的过程叫做线索化
加上了线索的二叉树就是_线索二叉树 _

为了区分二叉链表的中Lchild和Rchild是指向左右孩子还是前驱后继的,我们来添加tag来加以区分
ltag = 0 //指向左孩子
rtag = 0 // 指向右孩子
ltag = 1; //指向前驱
rtag = 1; //指向后继
//Code
typedef struct BiThrNode{
int data;
int ltag,rtag ; //表示符号
struct BiThrNode *lchild,rchild;
}BiThrNode.*BiThrTree;


先序序列: A B C D E

请根据以上的 二叉树,将其按先序、中序、后续、进行线索化;

review 树的定义

typedf struct PtNode{
TElemType data;
int parent ; //双亲位置域
}PTNode;
#define MAX_TREE_SIZE
//树
type struct{
PTNode nodes[MAX_TREE_SIZE];
int r,n ; //根节点的位置,和结点个数
}PTree;
找到双亲很容易,找孩子难。
把每个结点的孩子结点排列起来,看成一个线性表,用单链表进行存储,则n个结点就有n个孩子链表,(叶子结点的链表为空),而n个头指针又组成一个线性表,用顺序表进行存储。

//孩子结点
typedef struct CTNode{
int child ; //
struct CTNode *next;
}*ChildPtr;
//双亲结点
typedef struct{
TElemType data;//数据
ChildPtr fistchild; //孩子链表的头结点
}CTBox;
//树
typedef struct{
CTBox Nodes[MAXSIZE];
int n,r;//头结点位置和根节点位置
}
找孩子容易,找双亲难
实现: 用儿茶链表作树的存储结构,链表中每一个结点的两个指针域,
分别指向第一个孩子结点和下一个兄弟结点
typedef struct CSNodeP{
ElemType data;
struct CSNode *firstChild,*nextsibling;
}CSnode,*CSTree;

将树转换为二叉树进行处理,利用二叉树算法实现对树的操作。
二叉树,都是可以使用二叉链表作为存储结构,则二叉链表作为媒介可以导出树与二叉树之间的对应关系。
给定一颗树,我们就能将其转换为二叉树


兄弟相连接留长子




我们可以将森林看做三个部分

依次进行先序遍历

依次从左到右对森林每一个树进行后根遍历