树介绍
```
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <malloc.h>
/*
非线性结构之树
定义:
专业定义:
1.有且只有一个称为根的节点
2.有若干个互不相交的子树,这些子树本身也是一个树
通俗定义:
1.树是由节点和边组成
2.每一个节点只有一个父节点,可以有多个子节点
3.有一个节点例外,没有父节点,此节点为根节点
专业术语:
节点、父节点、子节点、子孙、堂兄弟、兄弟、深度(树的层数)
叶子节点(没有子节点的节点)、非终端节点(非叶子节点)
度(子节点的个数)、
分类:
一般树:
任意一个节点的子节点的个数不受限制
二叉树:
任意一个节点的子节点的个数最多两个,且子节点的位置不可更改
分类:
一般二叉树
满二叉树:
在不增加树的层数的前提下,无法再多添加
一个节点的二叉树。
完全二叉树:
如果只是删除了满二叉树最底层最右边的连续若干个节点,
这样形成的二叉树就是满二叉树。
森林:
n个互不相交的树的集合
树的存储:
二叉树的存储
连续存储[以完全二叉树的形式去存,保存树的结构,优点:1.由编号知层数 2.由编号知父节点与子节点]
优点:
查找某个节点的父节点和子节点速度很快
(也包括判断有没有父节点与子节点)
缺点:
耗用内存空间大
链式存储
一般树的存储(非线型用线型去存储)
双亲表示法:求父节点方便
孩子表示法:求子节点方便
双亲孩子表示法:求父节点与子节点都方便
二叉树表示法:
把一个普通树转化成二叉树来存储
具体转化方法:
设法保证任意一个节点的
左指针域指向它的第一个孩子(最左边),
右指针域指向它的兄弟,
只要能满足此条件,就可以转化为二叉树。
并且转化成的二叉树一定没有右子树
森林的存储
先把森林转化为二叉树,再存储二叉树
二叉树的操作:
1.遍历
先序遍历:
先访问根节点
再先序访问左子树
再先序访问右子树
中序遍历
中序遍历左子树
在访问根节点
再中序遍历右子树
后序遍历
中序遍历左子树
中序遍历右子树
再在访问根节点
2.已知两种遍历序列(先、中)(中、后)求原始二叉树
通过先序和中序 或者 中序和后序 我们可以还原出原始二叉树
但是通过后序与先序我们是无法推出的
①已知先序和中序求原始二叉树并进而求后序
先序:ABCDEFGH
中序:BDCEAFHG
求后序:EDCBHGFA
①已知后序和中序求原始二叉树并进而求先序
中序:BDCEAFHG
后序:DECBHGFA
求先序:ABCDEFGH
树的应用:
树是数据库中数据组织的一种重要的形式
操作系统父子进程的关系本身就是一棵树
面向对象语言中类的继承关系 本身也是一棵树
赫夫曼树
*/
/*
链式二叉树的实现
*/
# include <stdio.h>
# include <malloc.h>
struct BTNode
{
char data;
struct BTNode * pLchild; //p是指针 L是左 child是孩子
struct BTNode * pRchild;
};
void PostTraverseBTree(struct BTNode * pT);
struct BTNode * CreateBTree(void);
void PreTraverseBTree(struct BTNode * pT);
void InTraverseBTree(struct BTNode * pT);
int main(void)
{
struct BTNode * pT = CreateBTree();
// PreTraverseBTree(pT);
// InTraverseBTree(pT);
PostTraverseBTree(pT);
return 0;
}
void PostTraverseBTree(struct BTNode * pT)
{
if (NULL != pT)
{
if (NULL != pT->pLchild)
{
PostTraverseBTree(pT->pLchild);
}
if (NULL != pT->pRchild)
{
PostTraverseBTree(pT->pRchild);
//pT->pLchild可以代表整个左子树
}
printf("%c\n", pT->data);
}
}
void InTraverseBTree(struct BTNode * pT)
{
if (NULL != pT)
{
if (NULL != pT->pLchild)
{
InTraverseBTree(pT->pLchild);
}
printf("%c\n", pT->data);
if (NULL != pT->pRchild)
{
InTraverseBTree(pT->pRchild);
//pT->pLchild可以代表整个左子树
}
}
}
void PreTraverseBTree(struct BTNode * pT)
{
if (NULL != pT)
{
printf("%c\n", pT->data);
if (NULL != pT->pLchild)
{
PreTraverseBTree(pT->pLchild);
}
if (NULL != pT->pRchild)
{
PreTraverseBTree(pT->pRchild);
//pT->pLchild可以代表整个左子树
}
}
/*
伪算法
先访问根节点
再先序访问左子树
再先序访问右子树
*/
}
struct BTNode * CreateBTree(void)
{
struct BTNode * pA = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pB = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pC = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pD = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pE = (struct BTNode *)malloc(sizeof(struct BTNode));
pA->data = 'A';
pB->data = 'B';
pC->data = 'C';
pD->data = 'D';
pE->data = 'E';
pA->pLchild = pB;
pA->pRchild = pC;
pB->pLchild = pB->pRchild = NULL;
pC->pLchild = pD;
pC->pRchild = NULL;
pD->pLchild = NULL;
pD->pRchild = pE;
pE->pLchild = pE->pRchild = NULL;
return pA;
}
```
二叉树的建立
```
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <malloc.h>
typedef struct BiTNode
{
char data;
struct BiTNode *lchild, *rchild;
}BiTNode,*BiTree;
int main()
{
}
/*
按前序输入二叉树中结点的值(一个字符)
#表示空树,构造二叉链表表示二叉树T。
*/
void CreateBiTree(BiTree *T)
{
char ch;
scanf("%c",&ch);
if(ch=='#'){
*T=NULL;
}else{
*T = (BiTree)malloc(sizeof(BiTNode));
if(!*T){
exit(-1);
}
(*T)->data=ch;//生成根结点
CreateBiTree(&(*T)->lchild);//构造左子树
CreateBiTree(&(*T)->rchild);//构造右子树
}
}
```
线索二叉树
```
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <malloc.h>
/*
线索二叉树结构实现
遍历时发现相当于操作一个双向链表
*/
typedef enum(Link,Thread) PointerTag;
/*
Link==0 表示指向左右孩子指针
Thread==1 表示指向前驱或后继的线索
*/
typedef struct BiThrNode
{
char data;
struct BiTNode *lchild, *rchild;
PointerTag LTag;
PointerTag RTag;//左右标志
}BiThrNode,*BiThrTree;
int main()
{
}
BiThrTree pre;//全局变量,始终指向刚刚访问过的结点
//中序遍历进行中序线索化
void InThreading(BiThrTree p)
{
if(p){
InThreading(p->lchild);//递归左子树线索化
//*****************************************
/*
与中序遍历二叉树代码几乎一样,就是将打印部分换成了
线索化的功能
*/
//前驱线索化简单,某结点的左指针域为空,因为前驱结点刚刚访问过
//赋值给了pre,可以将p->lchild = pre,并修改p->LTag = Thread
if(!p->lchild){ //没有左孩子
p->LTag = Thread;//前驱线索化
p->lchild = pre;//左孩子指针指向前驱
}
//后继就要麻烦一点,因为后继结点还没有访问过
//所以只能对某节点p的前驱结点pre的右指针做判断
//1.右指针为空,则pre就是p的后继结点
if(!pre->rchild){//前驱没有右孩子
pre->RTag = Thread;//后继线索
pre->rchild=p;//前驱右孩子指针指向后继(当前结点p)
}
pre = p;//保持pre指向p的前驱
//*************************************
InThreading(p->rchild);//递归右子树线索化
}
}
/*T指向头结点,头结点左链lchild指向根结点,头结点右链rchild指向中序遍历的
最后一个结点。中序遍历二叉线索链表表示的二叉树T*/
Status InOrderTraverse_Thr(BiThrTree T)
{
BiThrTree p;
p = T->lchild;//P左指针指向根结点
while(p!=T){//空树或遍历结束时,p==T
while(p->LTag == Link){//当p->LTag==1时循环到中序序列第一个结点
p = p->lchild;
}
printf("%c",p->data);//显示结点数据,可以更改为其他对结点操作
while(p->RTag==Thread&&p->rchild!=T){
p = p->rchild;
printf("%c",p->data);
}
p = p->rchild;//p进至其右子树根
}
return OK;
}
```