/*
*
*【一】 了解二叉树的概念: 左子树,右子树,根,堂兄弟,左孩子,右孩子,父节点
*
性质1: 在二叉树的第i层上至多有2^i个节点
性质2: 深度为h的二叉树, 最多节点个数: 2^0 + 2^1 + 2^2 + ... 2^(h-1) = 2^h - 1
性质3: 【除了根节点没有双亲外,其他节点都有双亲】 节点数 = 分支数 + 1
*
满二叉树: 深度为k,且含有2^k -1个节点的二叉树,即不存在度为1的节点
完全二叉树: 树中所含的n个节点和满二叉树中编号从1至n的节点一一对应【即一定要按上到下,按做到右排列】
特性:
(1)如果有N个节点,那么它的深度D = [logN] + 1; //以2为底的log,对n求对数之后取整 +1
*(2)若对含n个节点的二叉树从上到下且从左到右进行1至n的编号【完全二叉树】,则对二叉树中任意一个编号为i的节点:
1.若i=1,则该节点为根节点,无双亲,否则编号为 i/2向下取整 的节点为其双亲节点
2.若2i>n,则该节点无左孩子,否则编号为2i的节点为其左孩子节点
3.若2i+1>n,则该节点无右孩子节点,否则编号为2i+1的节点为其右孩子节点
【二】 二叉树的链式存储:
1.二叉链表类型
typedef struct TriTNode
{
TElemType data; //数据域
struct TriTNode * lChild, *rChild; //指向左孩子,右孩子的指针
}TriTNode, *TriTree;
2.三叉链表类型:[比二叉链表类型多了个指向双亲节点的指针域]
typedef struct TriTNode
{
TElemType data; //数据域
struct TriTNode * lChild, *rChild; //指向左孩子,右孩子的指针
struct TriTNode * parent; //指向双亲节点的指针域
}TriTNode, *TriTree;
3.双亲链表类型
typedef struct BPTNode
{
TElemType data;
int * parent; //指向双亲的指针
char LRTag; //左右孩子标志域
}BPTNode; //节点类型
typedf struct BPTree
{
BPTNode nodes[MaxSize];
int num; //节点数目
int root; //根节点的位置
}BPTree; //二叉树类型
4.线索链表【以后讲解】
指向该线性序列中的"前驱"和"后继"的指针,称作"线索", 在存储结构里包含"线索"的话叫做"线索链表"
【三】二叉树的遍历
(1)先左后右的遍历算法
1.先序遍历
2.中序遍历
3.后序遍历
递归形式【主要】:
void Preorder(BiTree T, void(*visit)(TElemType &)e)
{ //先序遍历二叉树
if (T)
{
visit(T->data); //访问根节点
Preorder(T->lChild,visit); //遍历左子树
Preorder(T->rChild,visit); //遍历右子树
}
}
非递归形式[用栈]: //以中序遍历为例
当访问到一个根节点的时候,如果该节点有左子树,那么该节点入栈,如果左子树为null,则直接访问它
BiTNode * GoFarLeft(BiTree T, Stack * S)
{
if (!T) //节点本身为空,就返回NULL
return NULL;
while (T->lChild) //如果左子树不为空,那么一直入栈,直到空为止
{
Push(S, T); //入栈
T = T->lChild; //令T等于当前T的左子树
}
return T;
}
void InOrder(BiTree T, void (*visit)(TelemType& e))
{
Stack * S;
t = GoFarLeft(T, S); //找到最左下的节点
while (t)
{
visit(t->data);
if (t->rChild)
{
t = GoFarLeft(t->rChild, S);
}
else if (!StatckEmpty(S)) //栈不空的话, 可以退栈
{
t = Pop(S); //指向栈顶的节点
}
else
{
t = NULL; //栈空表明已经全部遍历结束了!
}
}
}
【四】用遍历算法解决问题
1.统计二叉树中叶子节点的个数(先序遍历),递归调用
void CountLeaf(BiTree T, int & count)
{
if (T)
{
if ((!T->lChild) && (!T->rChild)) //左子树,和右子树都空,那么是叶子节点
{
count++;
}
CountLeaf(T->lChild, count);
CountLeaf(T->rChild, count);
}
}
2.求二叉树的深度(后序遍历),递归调用
分析: 当前树的深度 = 子树深度的最大值 + 1
因为要得到左子树深度 和 右子树深度,比较得到最大值,所以用后序遍历
int Depth(BiTree T)
{
if (!T)
depthval = 0;
else
{
depthLeft = Depth(T->lChild);
depthRight = Depth(T->rChild);
depthval = 1 + (depthLeft > depthRight) ? depthLeft : depthRight;
}
return depthval;
}
3.复制二叉树(后序遍历)
//生成一个新的二叉树节点的算法:
BiTNode * GetTreeNode(TElemType item, BiTNode * lptr, BiTNode * rptr)
{
if (!(T = (BiTreeNode *)malloc(sizeof(BiTNde)))) //动态分配空间
exit(1);
T->data = item; //拷贝数据域
T->lChild = lptr;
T->rChild = rptr;
return T;
}
//复制算法:
BiTNode * CopyTree(BiTNode * T)
{
if (!T)
return NULL;
if (T->lChild)
newlptr = CopyTree(T->lChild);
else
newlptr = NULL; //左子树指针域指向空
if (T->rChild)
newrptr = CopyTree(T->rChild);
else
newrptr = NULL; //右子树指针域指向空
newNode = GetTreeNode(T->data, newlptr, newrptr);
return newNode;
}
4.建立二叉树的存储结构
①按给定的先序序列建立二叉链表
Status CreateBiTree(BiTree & T)
{
scanf(&ch); //如果输入的是' '空格符,就表示NULL
if (ch == ' ')
T = NULL; //返回根节点为空
else
{
if (!(T = (BiTNode *)malloc(sizeof(BiTNode))))
exit(OVERFLOW);
T->data = ch; //生成根节点
CreateBiTree(T->lChild); //构造左子树
CreateBiTree(T->rChild); //构造右子树
}
return OK;
} //CreateBiTree
②按给定的表达式建相应的二叉树[利用到了之前学过的栈把表达式转成后缀表达式 + 二叉树遍历]
//创建叶子节点的算法:
void CrtNode(BiTree & T, char ch)
{
T = (BiTNode *)malloc(sizeof(BiTNode));
T->data = ch;
T->lChild = T->rChild = NULL; //叶子节点的左右子树都应该是null
Push(PTR, T);
}
//建子树算法:
void CrtSubtree(Bitree & T, char c)
{
T = (BiTNode *)malloc(sizeof(BiTNode));
T->data = c;
Pop(PTR, rc); //把右子树指针退出来放在rc
T->rChild = rc;
Pop(PTR, lc); //把左子树指针退出来放在lc
T->lChild = lc;
Push(PTR, T);
}
void CrtExptree(BiTree & T, char exp[]) //表达式变二叉树的方法
{ //需要两个栈,一个存放运算符,另一个存放后缀表达式
InitStack(S); //初始化一个栈,放运算符
Push(S, '#'); //一开始放一个#在底部,它的优先级最低
InitStack(PTR); //表达式的栈
char * p = exp; //指针指向表达式
char ch = *p;
while (!(GetTop(S) == '#' && ch == '#')) //如果表达式还没有结束,且栈还没有到底部
{
if (!IN(ch, OP)) //如果不是运算符
{
CrtNode(t, ch); //建叶子节点并入栈
}
else //如果是运算符的话
{
switch(ch)
{
case '(': //左扩符
Push(S, ch);
break;
case ')': //遇到右扩号出栈到'('
{
Pop(S, c);
while(c != '(')
{
CrtSubtree(t, c);
//建立二叉树并入栈
Pop(S,c); //再继续弹出,直到遇到'('
}
}
break;
default: //其他运算符
{
while (!GetTop(S,c) && (precede(c,ch))) //比较当前从表达式提取出来的字符和栈顶字符哪个优先级比较高
{ //如果当前运算符比栈顶运算符的优先级低,那么栈顶出栈,直到栈顶元素优先级比当前运算符高才停止出栈
CrtSubtree(t, c);
Pop(S, c);
}
if (ch != '#') //如果当前字符是#就是说表达式结束了,否则表达式没结束,做完上面的while后,就可以安心入栈
{
Push(S, ch);
}
}
break;
}
}
}
}
③由先序遍历+中序遍历确定二叉树
例如:已知二叉树的
先序序列: abcdefg
中序序列: cdbaegf
分析: 根节点是 a, 左子树是bcd,右子树是egf
【五】线索二叉树【线索链表的遍历就不需要栈了】
(1)定义: 指向该线性序列中的"前驱"和"后继"的指针,称作"线索", 在存储结构里
包含"线索"的话叫做"线索链表",与其对应的二叉树,叫做"线索二叉树"
【有先序线索化二叉树,中序线索化二叉树,后序线索化二叉树】
前驱线索:加在节点的左子树为空的节点上
后继线索: 加在节点的右子树为空的节点上
线索链表的节点类型: 【比普通的二叉链表类型多了两个标志域】
在二叉链表的节点中增加两个标志域,并规定:
若该节点的左子树不空,则lChild域的指针指向其左子树,且左标志域的值为0
否则,lChild域的指针域指向前驱节点,且左标志域的值为1
若该节点的右子树不空,则rChild域的指针指向其右子树,且右标志域的值为0
否则,rChild域的指针域指向后继节点,且右标志域的值为1
类型定义:
typedef enum{Link,Thread}PointerThr; //枚举类型: Link == 0: 指针, Thread == 1: 线索
typedef struct BiThrNode
{
TElemType data;
struct BiThrNode * lChild, *rChild; //左右指针
PointerThr LTag, RTag; //左右标志域
}BiThrNode, *BiThrTree;
【注: 线索二叉树,加了个空的头结点Root】
(2)线索二叉树的遍历【中序线索化遍历】
Status InOrderTraverseThr(BiThrTree T.status, (*Visit)(TElemType e))
{
p = T->lChild; //T一开始是Root节点【空节点】,所以p指向根节点
while (p != T) //空树或遍历结束时,p == T
{
while (p->LTag == Link) //如果这个节点的左标志域 == Link,那么表示有左子树
{
//一直往下走,直到找到一个节点没有左子树,那么这个节点就是这棵树的第一次访问的节点【中序遍历】
p = p->lChild;
}
if (!Visit(p->data))
return ERROR;
while (p->RTag == Thread && p->rChild != T) //如果右标志域是线索域【即指向的是后继节点】,而且后继节点不是指向头指针【Root(空)】
{
p = p->rChild; //那么就让p指向它的后继节点,并且访问它的后继节点
Visit(p->data);
}
p = p->rChild; //p进至到右子树根,然后会遍历这颗右子树
}//while
return OK;
}
(3)如何对二叉树进行线索化【用先序遍历演示】
【递归调用方法】
void InThreading(BiThrTree p)
{
if (p)
{
//用pre指向当前节点的前驱节点,用p指向当前节点
InThreading(p->lChild); //右子树线索化
if (!p->lChild) //如果左子树为空
{
p->LTag = Thread; //左标志域 == 线索
p->lChild = pre; //建立前驱线索
}
if (!pre->rChild)
{
pre->RTag = Thread;
pre->rchild = p; //建立后继线索
}
pre = p; //保持pre指向p的前驱
InThreading(p->rChild); //右子树线索化
}
} //InThreading
Status InOrderThreading(BiThrTree & Thrt, BiThrTree T)
{
if (!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode))))
{
exit(OVERFLOW); //分配一个头节点【Root】
}
Thrt->LTag = Link;
Thrt->RTag = Thread;
Thrt->rChild = Thrt; //头结点的右线索初始的时候指向自己
if (!T) //如果是空树的话,头结点的左标志域应该改成Thread,然后指向自己
{
Thrt->LTag = Thread; //如果是空树,前驱后继都是自身
Thrt->lChild = Thrt;
}
else
{
Thrt->lChild = T; //头节点的左指针域指向二叉树的根【真正有值的树根】
pre = Thrt; //头结点[Root]保存为前驱节点【暂存下来】
InThreading(T); //中序线索化T
pre->rChild = Thrt;
pre->RTag = Thread;
Thrt->rChild = pre; //处理最后一个节点
return OK;
}
}
*/