一、树的定义
1.定义:n个系欸但那的有限集。N = 0时成为空树
非空树中:
(1)有且只有一个根节点
(2)n>1时其余系欸但可以分为m个互不相交的子树
2.节点分类
2.1节点的度:节点拥有子树的个数,度为0的节点成为叶子节点或者终端节点,不为0的节点称为分支节点,树的度时树内各节点度的最大值。
2.2节点之间的关系:
节点的子树称为该节点的孩子,称该节点为孩子的双亲,同一个双亲的孩子之间称为兄弟。
2.3节点的层次
节点的层次:从根开始定义起,根为第一层,根的孩子为第二层
双亲在同一层的节点称为堂兄弟。
有序树:各子树从左至右是有次序的,不能呼唤的,称该树有序。
森林:互不相交的树的集合。
二、树的存储结构
1.双亲表示法:
利用顺序数组进行树中元素的存储,利用这样一个结构体来实现节点的关系
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode//树节点的结构
{
TElemType data;
int parent;
}PTNode;
typdef struct //树的结构
{
PTNode nodes[MAX_TREE_SIZE];
Int r,n;//根的位置和节点数
}Ptree;
很容易找到双亲节点,但是要知道结点的孩子是什么需要遍历整个结构。
其他的类型:
添加一个右边兄弟的数据情况。
2.孩子表示法:
给所有的节点分配孩子变量,这样可能会造成空间的浪费。
//代码,双亲孩子表示法
结构 | 作用 |
---|---|
CTNode | 孩子节点,包含孩子节点的下标,指向下一个孩子的指针 |
CTbox | 表头节点,包含表头节点的数据,双亲坐标,表头指针 |
Tree | 包含表头数组,树的节点数和根的位置 |
#define MAX_TREE_SIZE 100
//孩子节点
typedef struct CTNode
{
int child;//孩子节点的下标
struct CTNode *next;//指向下一个孩子节点的指针
}*ChildPtr;
//表头节点
typedef struct
{
TElemType data; //存放在树中节点的数据
int parent; //存放双亲的位置下标
ChildPtr firstchild;//存放第一个孩子的指针
}CTbox;
//树结构
typedef struct
{
Ctbox nodes[MAX_TREE_SIZE 100];
int r,n;//根的位置和节点数
}Tree;
三、二叉树
1.二叉树的定义:n个节点的有限集合,一个根节点,子树只能是两个。
特点:
二叉树度不大于2,可以没有。左右子树存在顺序,不能颠倒。即使只有一棵子树,也要区分是左右。
五种形态:
2.特殊二叉树
2.1斜树
2.2满二叉树,所有的非叶子节点的度都是2,所有的叶子节点都在同一层上
2.3完全按二叉树,n个节点按层序编号,位置正确,顺序连贯,并且和满二叉树顺序相同。
3.二叉树性质
3.1第i层上至多有2^(i-1)个节点
3.2深度为k的二叉树至多有2^i - 1个节点
3.3二叉树终端节点n0,度为2的节点n2,n0 = n2+1
3.4具有n个节点的二叉树的深度为log2(n) + 1
3.5i的子节点为2i和2i+1
4.二叉树的存储结构
4.1顺序二叉树
4.2二叉链表
typedef struct BiTNode
{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
四、遍历二叉树
遍历二叉的过程使用了递归的过程,因为二叉树的建立过程中,节点之间的关系不存在前驱后继的关系,在访问一个节点之后都会产生新的选择。
1.先序遍历:若二叉树为空,则空操作返回,否则先访问根节点,接着前序遍历左子树,接着前序遍历右子树。
2.中序遍历:若树为空,则空操作返回,否则从根节点开始,中序遍历根节点的左子树,然后访问根节点,然后中序遍历右子树
3.后序遍历:若树为空,则空操作返回,否则从左至有先叶子后节点的方式访问左右子树,最后是访问根节点。
4.层序遍历:若树为空,则空操作返回,否则从上到下,从左至右,一层一层访问。
五、二叉树的建立和遍历算法
中序建立:创建根节点,创建左节点,创建右节点,使用递归的方法
遍历的方法也就是递归的过程:
1.先序遍历:若二叉树为空,则空操作返回,否则先访问根节点,接着前序遍历左子树,接着前序遍历右子树。
2.中序遍历:若树为空,则空操作返回,否则从根节点开始,中序遍历根节点的左子树,然后访问根节点,然后中序遍历右子树
3.后序遍历:若树为空,则空操作返回,否则从左至有先叶子后节点的方式访问左右子树,最后是访问根节点。
#include <iostream>
#include <malloc.h>
using namespace std;
typedef struct BiTNode
{
char data;
BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
void initBiTree(BiTree &T)
{
T = NULL;
}
void DestoryBiTree(BiTree &T)
{
if(T)
{
if(T->lchild)
DestoryBiTree(T->lchild);
if(T->rchild)
DestoryBiTree(T->rchild);
T = NULL;
}
}
void Visit(char c,int level)
{
cout << c <<"在层"<<level<<endl;
}
void InOrderTraverse(BiTree T,int level)
{
if(T)
{
InOrderTraverse(T->lchild,level+1);
Visit(T->data,level);
InOrderTraverse(T->rchild,level+1);
}
}
void CreateBiTree(BiTree &T)
{
char ch;
ch = getchar();
if(ch==' ') // 空
T=NULL;
else
{
T=(BiTree)malloc(sizeof(BiTNode)); // 生成根结点
if(!T)
exit(0);
T->data=ch;
CreateBiTree(T->lchild); // 构造左子树
CreateBiTree(T->rchild); // 构造右子树
}
}
void PreOrderTraverse(BiTree T,int level)
{
if(T) // T不空
{
Visit(T->data,level);
PreOrderTraverse(T->lchild,level+1);
PreOrderTraverse(T->rchild,level+1);
}
}
void back(BiTree T,int level)
{
if(T)
{
back(T->lchild,level+1);
back(T->rchild,level+1);
Visit(T->data,level);
}
}
int main(int argc, char** argv) {
BiTree T;
initBiTree(T);
CreateBiTree(T);
int level = 1;
cout <<"中序遍历" <<endl;
InOrderTraverse(T,level);
cout <<"前序遍历" <<endl;
level = 1;
PreOrderTraverse(T,level);
cout <<"后序遍历" <<endl;
level = 1;
back(T,level);
DestoryBiTree(T);
return 0;
}
六、线索二叉树
节省空节点的问题,二叉树的结构中包含了左右孩子,如果没有左右孩子,则会浪费了这个空间。
在中序遍历时,可以采用一种结构,设置了前驱和后继,将二叉树联系起来
data | LTag | lchild | rchild | RTag |
---|
LTag 和 RTag 为 0 时指向的是孩子,为 1 时指向的是前后继。
设置一个头节点,头节点的左孩子指向根节点,头节点的右孩子指向遍历的最后一个节点;第一个访问的节点的左孩子(前驱)指向头节点;最后一个节点的右孩子(后继)指向这个头节点。
#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
typedef enum {Link,Thread} PointerTag;//Link = 0,Thread = 1
//LTag 和 RTag 为 0 时指向的是孩子,为 1 时指向的是前后继。
typedef struct BiTrNode
{
ElemType data;
struct BiTrNode *lchild,*rchild;
PointerTag LTag;
PointerTag RTag;
}BiThrNode,*BiThrTree;
//创建一棵二叉树,约定用户遵照前序遍历的方式输入数据
void CreateBiThrTree(BiThrTree &T)
{
char c;
scanf("%c",&c);
if(' ' == c)
T = NULL;
else
{
T = (BiThrTree)malloc(sizeof(BiThrNode));
T->data = c;
T->LTag = Link;//默认都为孩子,之后我们通过一个函数进行修改
T->RTag = Link;
CreateBiThrTree(T->lchild);
CreateBiThrTree(T->rchild);
}
}
//全局变量,始终指向刚刚访问过的节点
BiThrTree pre;
void InThreading(BiThrTree &T)
{
//中序遍历线索化
if(T)
{
InThreading(T->lchild);//递归左孩子线索化
//节点处理
if (!T ->lchild)//如果该节点没有左孩子,设置LTag为Thread并且把lchild指向上一个访问的节点
{
T->LTag = Thread;
T->lchild = pre;
}
if(!pre->rchild)
{
pre->RTag = Thread;
pre->rchild = T;
}
pre = T;
InThreading(T->rchild);//递归右孩子线索化
}
}
void InOrderThreading(BiThrTree &p,BiThrTree &T)
{
p = (BiThrTree)malloc(sizeof(BiThrNode));
p->LTag = Link;
p->RTag = Thread;
p->rchild = p;
if(!T)
p->lchild = p;
else
{
p->lchild = T;
pre = p;
InThreading(T);
pre -> rchild = p;
pre -> RTag = Thread;
p->rchild = pre;
}
}
void visit(char c)
{
printf("%c ",c);
}
//中序遍历二叉树非递归
void InOrderTraverse(BiThrTree T)
{
BiThrTree p;
p = T->lchild;
while(p!=T)
{
while(p->LTag == Link)
{
p = p->lchild;
}
visit(p->data);
while (p->RTag == Thread&&p->rchild!=T)
{
p = p->rchild;
visit(p->data);
}
p = p->rchild;
}
}
int main()
{
BiThrTree p,T = NULL;
CreateBiThrTree(T);
InOrderThreading(p,T);
printf("中序遍历输出结果为:");
InOrderTraverse(p);//p是头指针
return 0;
}
七、树和森林的转换
普通树转化为二叉树:
1.兄弟之间加连线
2.去除长子之外的连线、调整
森林转为二叉树:
1.每棵树变为二叉树
2.将根依次连线
二叉树到树到森林的转换:
1.若节点x是其双亲y的左孩子,则把x的右孩子,右孩子的右孩子,…都与y用连线连起来
2.去除双亲到右孩子之间的连线。
判断二叉树是否能转换称森林:是否有右孩子,有的话就是森林。
树与森林的遍历:
先根遍历:先访问树的根节点,然后一次遍历子树
后根遍历:先子树,再根