数据结构学习:二叉树

数据结构学习:二叉树

二叉树是n(n>=0)个结点的有限集合:
1.空二叉树的n=0
2.由一个根节点和两个互不相交的被称为跟的左右子树组成。左子树和右子树又分别是一颗二叉树。
特点:
每个结点最多有两棵子树
左右子树不能颠倒(有序)

满二叉树:
一个高度为h,且含有2^h-1个结点的二叉树
特点:
只有最后一层又叶子结点,其他结点都有两个子结点
不存在度为1 的结点
按层序从左至右从1开始编号,结点i的左孩子为2i,有孩子为2i+1,结点i的父节点为[i/2](向下取整)
(如果有的话)。
在这里插入图片描述

完全二叉树
当且仅当其中每个结点都与高度h的满二叉树中的结点一一对应
即将满二叉树的最后一层删去若干(>=0)个结点,就是完全二叉树结点
特点:
只有最后两层可能有叶子结点
最多只有一个度为1 的结点
按层序从左至右从1开始编号,结点i的左孩子为2i,有孩子为2i+1,结点i的父节点为[i/2](向下取整)
(如果有的话)。
i<=[n/2]为分支结点,i>[n/2]为叶子结点

二叉排序树:
左子树上所有结点的关键字均小于根结点的关键字
右子树上所有结点的关键字均大于根结点的关键字
左子树和右子树又各是一棵二叉排序树
在这里插入图片描述

平衡二叉树:
树上任一结点的左子树和右子树的深度之差不超过1

平衡二叉树能有更高的搜索效率

设非空二叉树中度为:0,1,2的结点个数分别为n0,n1,n2
则:n0 = n2 + 1(叶子结点比二分支结点多一个

设树中结点的总数为n,则
1,n = n0 + n1 +n2
2,n = n1 +2n2 + 1 (树的结点树 = 总度数 + 1)
两式联立可得n0 = n2 + 1

高度为h的二叉树至多有2^h-1个结点
在这里插入图片描述

计算思路:n小于高度为h的满二叉树结点树,大于高度为h-1的满二叉树结点树

对于完全二叉树,可以由总的结点数n推出度为0,1,2的结点个数n0,n1,n2
完全二叉树最多只有一个度为1的结点,即n1 = 0或1
n0 = n2 + 1 -> n0 + n2 一定是奇数
->(可知)
若完全二叉树有2k个结点,则n1 = 1, n0 = k, n2 = k-1
若完全二叉树有2k-1个结点,则n1 = 0, n0 = k, n2 = k-1

二叉树的链式存储

struct ElemType
{
	int value;
};
//二叉树的结点(链式存储)
typedef struct BiTNode
{
	ElemType data;						//二叉树数据域
	struct BiTNode *lchild,*rchild;		//左结点和右结点
}BiTNode,*BiTree;
//定义一棵空树
BiTree root = NULL;

// 插入根节点
root = (BiTree)malloc(sizeof(BiTNode));
root->data = {1};
root->lchild = NULL;
root->rchild = NULL;

// 插入新结点
BiTNode *p = (BiTNode *)malloc(sizeof(BiTNode));
p->data = {2};
p->lchild = NULL;
p->rchild = NULL;
root->lchild = p;	//把p结点插入为根节点的左孩子

如果经常要用到一个结点的父结点的话,可以在定义结点是加入一个指向父结点的指针

遍历:按照某种次序把所有结点都访问一遍

层次遍历:基于树的层次特性确定的次序规则

先序遍历:先根,再左,后右

中序遍历:先左,再根,后右

后序遍历:先左,再右,后跟

先序/中序/后序遍历的先/中/后指的是根结点的遍历次序
先序遍历:第一次路过访问结点

中序遍历:第二次路过访问结点

后序遍历:第三次路过访问结点

Eg:
在这里插入图片描述
先序遍历:A B C
中序遍历:B A C
后序遍历:B C A
在这里插入图片描述
先序遍历:A B D E C F G
中序遍历:D B E A F C G
后序遍历:D E B F G C A
在这里插入图片描述
先序遍历:A B D G E C F
中序遍历:D G B E A F C
后序遍历:G D E B F C A

在这里插入图片描述

代码实现
先序遍历:
1.若二叉树为空,则什么也不做
2.若二叉树非空:
①.先访问根节点
②.先序遍历左子树
③.先序遍历右子树

// 先序遍历
void ProOrder(BiTree T)
{
	if(T != NULL)
	{
		visit(T);		//访问根结点
		ProOrder(T->lchild);	//递归遍历左子树
		ProOrder(T->rchild);	//递归遍历右子树
	}
}

中序遍历:
1.若二叉树为空,则什么也不做
2.若二叉树非空:
①.中序遍历左子树
②.先访问根节点
③.中序遍历右子树

// 中序遍历
void InOrder(BiTree T)
{
	if(T != NULL)
	{
		InOrder(T->lchild);	//递归遍历左子树
		visit(T);		//访问根结点
		InOrder(T->rchild);	//递归遍历右子树
	}
}

后序遍历:
1.若二叉树为空,则什么也不做
2.若二叉树非空:
①.后序遍历左子树
②.后序遍历右子树
③.先访问根节点

// 后序遍历
void PostOrder(BiTree T)
{
	if(T != NULL)
	{
		PostOrder(T->lchild);	//递归遍历左子树
		PostOrder(T->rchild);	//递归遍历右子树
		visit(T);			//访问根结点
	}
}
// 求树的深度
int treeDepth(BiTree T)
{
	if(T == NULL)
	{
		return 0;
	}
	else
	{
		int l = treeDepth(T->lchild);
		int r = treeDepth(T->rchild);
		// 树的深度 = Max(左子树的深度,右子树的深度)+1
		return l>r ? l + 1: r + 0;
	}
}

层序遍历
算法思想:
1.初始化一个辅助队列
2.根节点入队
3.若队伍为空,则队头结点出队,访问该结点,并将其左右孩子插入队尾(如果有点话)
4.重复3直至队列为空

// 层序遍历
void LevelOrder(BiTree T)
{
	LinkQuene Q;
	InitQuene Q;		//定义一个辅助队列并初始化
	BiTree p;			//定义一个指针指向树的当前结点
	EnQuene(Q,T);
	while(!IsEmpty(Q))
	{
		DeQuene(Q,p);	//队头结点出队
		visit(p);		//访问出队的结点
		if(p->lchild != NULL)
			EnQuene(Q,p->lchild);	//左孩子入队
		if(p->rchild != NULL)
			EnQuene(Q,p->rchild);	//右孩子入队
	}
}

根据遍历序列构造二叉树

一个遍历序列可能对应多种二叉树

由二叉树遍历序列构造二叉树至少需要:
1.前序 + 中序
2.后序 + 中序
3.层序 + 中序
一定要有中序序列才能构造二叉树

1.前序 + 中序:
在这里插入图片描述

Eg:
前序遍历序列:D A E F B C H G I
中序遍历序列:E A F D H C B G I

1.D为根节点,E A F为D的左子树结点,H C B G I为D的右子树结点
2.A为D的左孩子,E为A的左孩子,F为A的右孩子
3.B为D的右孩子,HC为B的左子树结点,G I为B的右子树结点
4.C为B的左孩子,H为C的左孩子,C的右孩子为空
5.G为B的右孩子,I为G的右孩子,G的左孩子为空

在这里插入图片描述

2.后序 + 中序
在这里插入图片描述

后序遍历的最后一个是二叉树的根节点
后序:E F A H C I G B D
中序:E A F D H C B H I
在这里插入图片描述

3.层序 + 中序
在这里插入图片描述

层序遍历序列的第一个结点为二叉树的根节点
Eg:
层序:A B C D E
中序:A C B E D
在这里插入图片描述

线索二叉树
方便找到一个结点的前驱和后继
在定义二叉树结点时,多定义两个指针来指向结点的前驱和后继
在这里插入图片描述

中序线索化

// 线索二叉树结点
typedef struct ThreadNode
{
	ElemType data;
	struct ThreadNode *lchild, *rchild;
	int ltag,rtag = 0;		//左右线索标志,当标志为1时,孩子指针指向线索
}ThreadNode, * ThreadTree;

// 全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;

// 中序遍历二叉树,一边遍历一边线索化
void InThread(BiTree T)
{
	if(T != NULL)
	{
		InThread(T->lchild);	//递归遍历左子树
		visit(T);			//访问根结点
		InThread(T->rchild);	//递归遍历右子树
	}
}

void visit(ThreadNode * q)
{
	if(q->lchild == NULL )		//左子树为空,建立前驱线索
	{
		q->lchild = pre;		
		q->ltag = 1;
	}
	if(pre != NULL && pre->rchild == NULL)
	{
		pre->rchild = q;	//建立前驱结点的后继线索
		pre->rtag = 1;
	}
	pre = q;	
}


// 中序线索化二叉树T
void CreateInThread(ThreadTree T)
{
	pre = NULL;				//pre初始值为NULL
	if(T != NULL)			//非空二叉树才能线索化
	{
		InThread(T);		//中序线索化二叉树
		if(pre->rchild) == NULL)
			pre->rtag = 1;	//处理遍历的最后一个结点
	}
}
// 先序线索化
// 线索二叉树结点
typedef struct ThreadNode
{
	ElemType data;
	struct ThreadNode *lchild, *rchild;
	int ltag,rtag ;		//左右线索标志,当标志为1时,孩子指针指向线索
}ThreadNode, * ThreadTree;

// 全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;

// 先序遍历二叉树,一边遍历一边线索化(与中序的区别)
void PreThread(BiTree T)
{
	if(T != NULL)
	{
		visit(T);			//访问根结点
		if(T->ltag == 0)	//左孩子不是线索
		{
			PreThread(T->lchild);	//递归遍历左子树
		}
		PreThread(T->rchild);	//递归遍历右子树
	}
}

void visit(ThreadNode * q)
{
	if(q->lchild == NULL )		//左子树为空,建立前驱线索
	{
		q->lchild = pre;		
		q->ltag = 1;
	}
	if(pre != NULL && pre->rchild == NULL)
	{
		pre->rchild = q;	//建立前驱结点的后继线索
		pre->rtag = 1;
	}
	pre = q;	
}
// 中序线索化二叉树T
void CreateInThread(ThreadTree T)
{
	pre = NULL;				//pre初始值为NULL
	if(T != NULL)			//非空二叉树才能线索化
	{
		PreThread(T);		//中序线索化二叉树
		if(pre->rchild) == NULL)
			pre->rtag = 1;	//处理遍历的最后一个结点
	}
}

在中序二叉树中找到指定结点*p的中序后继next
1.若 p->rtag == 1,则next = p->rchild
2.若 p->rtag ==0

// 找中序后继
// 找到以P为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p)
{
	// 循环找到最左下结点(不一定是叶结点)
	while(p->ltag == 0)
		p = p->lchild;
	return p;
}
// 在中序线索二叉树中找到结点p的后继结点
ThreadNode *Nextnode(ThreadNode *p)
{
	// 右子树中最左下结点
	if(p->rtag == 0)
		return Firstnode(p->rchild);//p的右孩子的最左下结点
	else
		return p->rchild;	//rtag = 1,则直接返回后继线索
}

// 对中序线索二叉树进行中序遍历(利用线索实现的非递归算法)
void Inorder(ThreadNode *T)
{
	for(ThreadNode *p = Firstnode(T);p != NULL;p = Nextnode(p))
		visit(p);
}

// 找中序前驱
// 找到以P为根的子树中,最后一个被中序遍历的结点
ThreadNode *Lastnode(ThreadNode *p)
{
	// 循环找到最左下结点(不一定是叶结点)
	while(p->rtag == 0)
		p = p->rchild;
	return p;
}
// 在中序线索二叉树中找到结点p的前驱结点
ThreadNode *Prenode(ThreadNode *p)
{
	// 右子树中最左下结点
	if(p->ltag == 0)
		return Lastnode(p->lchild);//p的左孩子的最右下结点
	else
		return p->lchild;	//rtag = 1,则直接返回前驱线索
}
// 对中序线索二叉树进行逆向中序遍历(利用线索实现的非递归算法)
void RevInorder(ThreadNode *T)
{
	for(ThreadNode *p = Lastnode(T);p != NULL;p = Prenode(p))
		visit(p);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小二康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值