树的基本术语
结点:数据元素及指向子树的分支
根节点:非空树中无前驱结点的节点
结点的度:结点拥有子树的个数
树的度:树内各节点度的最大值
叶子节点:度为零
分支节点:度不为0
内部节点:根节点以外的分支节点
森林:m(m>0)棵互不相交的树的集合。
二叉树
任何树都可以与二叉树相互转换,解决了树的存储结构及其运算存在的复杂性。
【说明】二叉树不是树的特殊概念,他们是两个概念。
二叉树结点的子树要区分左子树和右子树,即使只有一棵子树也要区分。
而树只有一个孩子时,无需区分左右次序。
二叉树的性质
- 第i层上至多有2^(i -1 )个节点
- 深度为k的二叉树至多有2^k -1 个结点
- 叶子数为n0,度为2的节点数为n2,则n0 = n2 + 1
满二叉树
深度为k且有2^k - 1个结点的二叉树
对位置进行编号:自根节点,自上而下,自左而右
完全二叉树
深度为k,具有n个结点的二叉树,当且仅当每一个结点都与深度为k的满二叉树中编号1-n的结点一一对应时,成为完全二叉树。
满二叉树从最后一个结点开始,连续去掉任意个结点,得到的是一课完全二叉树。
n个结点的完全二叉树深度为(log2N)的底 + 1
二叉树的存储结构
顺序存储结构
数组:按满二叉树的结点编号,编号为数组下标存放。
缺点:浪费空间,适合满二叉树和完全二叉树
链式存储结构
二叉链表
typedef struct BiNode{
TElemType data;
struct BiNode *Lchild, *Rchile; //左右孩子指针。
}BiNode, *BiTree;
在n个结点的二叉链表中,有____个空指针域
分析:必有n个链域。
除根节点外每个节点有且只有一个双亲,所以只有n-1个节点的链域存放指针
所以空指针数目为 2n - (n - 1) = n + 1 个
三叉链表
typedef struct TriTNode{
TElemType data;
struct TriTNode *Lchild, *parent, *Rchile; //左右孩子双亲指针。
}TriTNode, *TriTree;
二叉树的遍历
DLR——先序遍历
LDR——中序遍历
LRD——后序遍历
已知先序和中序,可以构造出相应二叉树(或者中序和后序)
先序遍历递归算法
Statue PreOrderTraverse(BiTree T){
if(T == NULL) return OK;
else{
visit(T);//访问根节点
// 输出根节点 printf("%d\t",T -> data);
PreOrderTraverse(T -> lchild);//递归遍历左子树
PreOrderTraverse(T -> rchild);//递归遍历右子树
}
}
从起点到终点,每个节点经过3次。
第一次经过时访问:先序遍历
第二次经过时访问:中序遍历
第三次经过时访问:后序遍历
时间效率:O(n)//每个结点只访问一次
空间效率:O(n)//栈占用的最大辅助空间
中序遍历非递归算法
Status InOrderTraverse(BiTree T){
BiTree p;
InitStack(S);
p = T;
while( p || !StackEmpty(S) ){ //p不为空或栈不为空
if(p){//根节点入栈,访问左子树
push(S,p); p = p->lchild;}
else{
pop(S,q); //弹出栈顶放入q
printf("%c",q->data);
p = q -> rchild;} //p访问右子树
}
return OK;
}
二叉树的层次遍历
实现:顺序的循环队列
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);
} //有右孩子就将右孩子进队
}
二叉树遍历算法的应用
建立二叉树:先序遍历建立二叉树的二叉链表
按顺序读入字符:ABC##DE#G##F###
Status CreateBiTree(BiTree &T){
scanf(&ch);
if(ch == '#') T = NULL;
else{
if(!(T = (BiTNode*)malloc(sizeof(BiTNode))))
exit(OVERFLOW);
T->data = ch; //生成根节点
CreateBiTree(T->lchild); //构造左子树
CreateBiTree(T->rchild); //构造右子树
}
return OK;
}
复制二叉树:先序遍历复制
int Copy(BiTree T, BiTree &NewT){
if(T == NULL){
New T = NULL;
return 0 ;
}
else{
NewT = new BiTNode; //分配新空间
NewT->data = T->data;
Copy(T->lchild, New->lchild);
Copy(T->rchild, New->rchild);
}
}
计算二叉树的深度
空树:深度为0;
有结点:递归计算左子树深度m,递归计算右子树深度n,二叉树深度为m和n较大数加一。
int Depth(BiTree T){
if(T = NULL) return 0;
else{
m = Depth(T->lchild);
n = Depth(T->rchild);
if(m>n) return (m+1);
else return (n+1);
}
}
计算二叉树结点总数
空树:节点数为0;
否则:节点数为左子树节点个数+右子树节点个数+1;
int NodeCount(BiTree T){
if(T == NULL) return 0;
else
return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
}
计算二叉树叶子节点数
空树:叶子节点为0;
否则:节点数= 左子树叶子节点数+右子树叶子节点个数
int LeadCount(BiTree T){
if(T == NULL) return 0; //空树返回0;
if(T->lchild == NULL && T->rchild == NULL) return 1;
//叶子结点返回1
else //不是叶子节点
return LeafCount(T->lchild) + LeafCount(T->rchild);
}
线索二叉树
用二叉链表作为二叉树的存储结构时,找某个结点的左右孩子很方便。
但是无法直接找到该结点在某种遍历序列的前驱和后继结点。
利用二叉链表的空指针域:
若某节点的左孩子为空,将空的左孩子的指针域改为指向其前驱;
若某节点的右孩子为空,将空的右孩子指针域改为指向其后继。
对二叉树按某种遍历次序使其变成线索二叉树的过程叫线索化。
为区分lchild和rchild指针到底是指向孩子的指针,还是指向前驱或后继的指针,对二叉链表中每个结点增设两个标志域ltag
和rtag
:
ltag
= 0 lchild
指向该节点的左孩子
ltag
= 1 lchild
指向该节点的前驱
rtag
= 0 rchild
指向该节点的右孩子
rtag
= 1 rchild
指向该节点的后继
typedef struct BiThrNode{
int data;
int ltag, rtag;
struct BiThrNode *lchild, rchild;
}BiThrNode, *BiThrTree;
增设一个头节点:
ltag = 0,lchild指向根节点;
rtag = 1,rchild指向遍历序列中最后一个结点
遍历序列中第一个结点的lchild和最后一个结点的rchild都指向头节点。
树和森林
森林:m(m>=0)棵互不相交的树
树的存储表示
双亲表示法(找双亲容易,找孩子难)
实现:结构数组存放树的结点,每个结点包含两个域。
数据域:存放结点本身信息;
双亲域:本节点的双亲结点在数组中的位置。(根的值为-1)
typedef struct PTNode{
TElemType data;
int parent;
}PTNode;
//定义一棵树
#define MAX_TREE_SIZE 100
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int r, n; //根节点位置和节点个数
}PTree;
孩子链表(找孩子容易,找双亲难)
实现:把每个结点的孩子结点排列起来,用单链表存储。(叶子节点的孩子链表为空表)。
n个头指针组成一个线性表,用顺序表(含n个元素的结构数组)存储。
孩子节点结构:
typedef struct CTNode{
int child;
struct CTNode *next;
}*ChildPtr;
双亲结点结构:
typedef struct{
TELemType data;
ChildPtr firstchild;
}CTBox;
树结构:
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n, r; //结点数和根节点的位置
}CTree;
带双亲的孩子链表:
在树结构再增加一个元素,存储双亲的下标
孩子兄弟表示法(二叉树表示法)
实现:用二叉链表作为树的存储结构,链表中每个节点的两个指针域分别指向第一个孩子节点和下一个兄弟结点。
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild,*nextsibling;
}CSNode, *CSTree;
树与二叉树的转换
以二叉链表作媒介,将树转化为二叉树操作,操作完成后,再将二叉树转换为树。
树变二叉树:兄弟相连留长子;
二叉树变树:左孩右右连双亲,去掉原来右孩线。
森林与二叉树的转换
每棵树分别转换为二叉树,第一棵树的根节点为二叉树的根。
森林变二叉树: 树变二叉根相连
二叉树变森林:去掉全部右孩线,孤立二叉再还原。
树与森林的遍历
1.树的遍历(三种方法)
先序遍历
后序遍历
层次遍历:从上到下,从左到右访问树的每个节点
2.森林的遍历
把森林看成三部分:
- 森林中第一棵树的根节点;
- 森林第一棵树的子树森林;
- 森林中其他树构成的森林。
先序遍历:123(对所有的树依次进行先序遍历)
中序遍历