树——“一对多”的数据关系
1.概念
1.结点的度:结点拥有的子树数。度为0的结点称为叶结点或终端结点,不为0的称为分支节点,除根节点外,分支节点也称为内部结点。
2.树的度:树内各节点的度的最大值。
注意:
1.树的关系是一对多,之前研究的线性表是一对一,不要混淆
2.当树的结点数n>0时,有且只有一个根节点
3.子数个数m>0时,子树的数目没有限制,但每个子树之间必须是不相交,若有相交,则不符合树的定义
2.相关代码操作
树的结构代码——双亲表示法
#define MAX_TREE_SIZE 100
typedef int Elemtype
typedef struct PTNode
{
Elemtype data;//结点数据
int parent;//双亲位置
}PTNode;
typedef struct PTree
{
PTNode nodes[MAX_TREE_SIZE];//结构体数组
int r;//根的位置
int n;//结点数目
}PTree;
树的结构代码——双亲-孩子表示法
#define MAX_TREE_SIZE 100
typedef struct CTNode//孩子结点
{
int child;//孩子结点的下标
struct CTNode* next;//指向下一个孩子结点的指针
} *ChildPtr;
typedef struct//表头结构
{
ElemType data;//存放在树中的结点的数据
int parent;//结点双亲的下标
ChildPtr firstchlid;//指向第一个孩子的指针
}CTBox;
typedef struct//定义树结构
{
CTBox nodes[MAX_TREE_SIZE];//结点数组
int r,n;//根的位置和结点数
}CTree;
树的结构代码——孩子-兄弟表示法(所有的树都可以通过该方法变为二叉树结构)
typedef struct CSNode
{
Elemtype data;
struct CSNode *firstchild, *rightsib;
}CSNode; *CSTree;
3.二叉树——每个结点的度必须小于等于2
3.1五种基本形态:
1.空二叉树
2.只有一个根节点
3.根节点只有左子树
4.根节点只有右子树
5.根既有左子树又有右子树
3.2特殊二叉树
1.斜树:所有的结点都只有左子树(左斜树)或只有右子树(右斜树)
**2.满二叉树:所有的结点都存在左子树和右子树,并且所有叶子都在同一层上,且都在最后一层 **
3.完全二叉树:先按层对树A每个结点进行编号,然后在另一个有相同深度的满二叉树B上也进行按层编号,如果同一个结点在两棵树上的编号相等,那么这颗树A就是完全二叉树。所以满二叉树一定是完全二叉树,但是完全二叉树不一定是满二叉树
3.3二叉树的存储结构——链式存储(二叉链表)
结构代码
typedef struct BiTNode
{
ElemType data;
struct BiTNode *lchild, *rchild;//定义左右子树
}BiTNode, *BiTree;
3.4 遍历二叉树——从根节点出发,按照次序依次访问二叉树中所有的结点,使得每个结点都被访问依次且仅被访问一次**
遍历方法(如果限制了从左到右的习惯方式):
1.前序遍历:若二叉树为空,则返回空操作,否则先访问根节点,然后前序遍历左子树,再前序遍历右子树。遍历的优先级为:根节点>左子树>右子树。
2.中序遍历:若二叉树为空,则返回空操作,否则从根节点开始(不是先访问根节点),中序遍历根节点的左子树,然后访问根节点,最后中序遍历右子树。遍历优先级:左子树>根节点>右子树。
3.后序遍历:若二叉树为空,则返回空操作,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根节点。遍历优先级:左子树>右子树>根节点
4.层序遍历:若二叉树为空,则返回空操作,否则从树的第一层开始,也就是根节点开始访问,从上而下逐层遍历,同一层中,按从左到右的顺序对结点逐个访问。
3.4.1 遍历算法——递归的思想
创建二叉树
void CreateTree(BiTree *T)
{
char c;
printf("input the char:\n");
scanf("%c",&c);
if(c == ' ')
(*T) = NULL;
else
{
*T = (BiTNode*)malloc(sizeof(BiTNode));//分配内存,将malloc()返回的指针强制转换为指向BiTNode型
(*T)->data=c;
CreateTree(&((*T)->lchild));//递归方法
CreateTree(&((*T)->rchild));
}
}
遍历二叉树——前序遍历,遍历的优先级为:根节点>左子树>右子树。
void PreOrderTraverse(BiTree T)
{
if(T == NULL)
return
else
{
printf("%c",T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
遍历二叉树——中序遍历,遍历优先级:左子树>根节点>右子树。
void PreOrderTraverse(BiTree T)
{
if(T == NULL)
return
else
{
PreOrderTraverse(T->lchild);
printf("%c",T->data);
PreOrderTraverse(T->rchild);
}
}
遍历二叉树——后序遍历,遍历优先级:左子树>右子树>根节点。
void PreOrderTraverse(BiTree T)
{
if(T == NULL)
return
else
{
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
printf("%c",T->data);
}
}
3.5 线索二叉树——每个结点中带有指向前驱和后继的指针
如下图所示,Ltag控制lchild的指向,若为0,指向左孩子,若为1,指向前驱,rtag同理
3.5.1 线索二叉树结构代码
typedef enum {Link, Thread} PointerNag;//定义枚举
typedef struct BiTNode
{
Elemtype data;
struct BiTNode *lchild, *rchild;
/*
定义两个枚举变量,若枚举值等于Link,则代表指向孩子,若枚举值等于Thread则代表指向前驱或者后继
*/
PointerNag ltag;
PointerNag rtag;
}BiThrNode, *BiThrTree;
创建二叉树
void CreateThrTree(BiThrTree* T)
{
char c;
scanf_s("%c",&c,1);
if (c == ' ')
(*T) = NULL;
else
{
(*T) = (BiThrTree)malloc(sizeof(BiThrNode));
(*T)->data = c;
(*T)->ltag = Link;//初始化将ltag和rtag都设为Link,即指向左右孩子
(*T)->rtag = Link;
CreateTree(&((*T)->lchild));
CreateTree(&((*T)->rchild));
}
}
二叉树遍历——将二叉树线索化,改变指针指向位置
void InThreading(BiThrTree* T)
{
if (!(*T))
return;
else
{
InThreading(&((*T)->lchild));
if ((*T)->lchild == NULL)
{
(*T)->ltag = Thread;
(*T)->lchild = pre;
}
if (pre->rchild == NULL)
{
pre->rtag = Thread;
pre->rchild = (*T);
}
printf("%c", (*T)->data);
pre = *T;
InThreading(&((*T)->rchild));
}
}
由于线索二叉树的运行过程需要设置一个前驱,因此在程序初始设置一个全局变量,初始化该全局变量时,指向全树的根节点
BiThrTree pre;
void InorderThreading(BiThrTree* p, BiThrTree* T)
{
*p = (BiThrNode*)malloc(sizeof(BiThrTree));//创建一个头结点
(*p)->ltag = Link;
(*p)->rtag = Thread;
if (!T)
(*p)->rchild = *p;
else
{
(*p)->lchild = *T;//头节点指向整棵树的根节点
pre = *p;//将该头结点赋值给pre
InThreading(T);//遍历头结点
pre->rchild = (*p);
pre->rtag = Thread;
(*p)->rchild = pre;
}
}