二叉树详细学习【线索二叉树还有点迷糊】

本文深入介绍了二叉树的基本概念,包括二叉树的性质、存储结构、遍历方法及其应用等核心内容。同时,详细解释了如何通过遍历算法解决实际问题,并探讨了线索二叉树的概念与实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*
*
*【一】	了解二叉树的概念: 左子树,右子树,根,堂兄弟,左孩子,右孩子,父节点
*	
	性质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;
	}
}

*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值