二叉树构建和基本操作【数据结构】

二叉树的基本概念介绍可以移步:点击打开链接

最终完整代码详见:点击打开链接

二叉树的创建:先序遍历的应用

二叉树结点的结构:

typedef char BTDataType;

typedef struct BinTreeNode{
	struct BinTreeNode* _pLeft;
	struct BinTreeNode* _pRight;
	BTDataType _data;
}BTNode, *pBTNode;
二叉树结点的创建:

pBTNode BTBuyNode(BTDataType data)
{
	pBTNode pNewNode = (pBTNode)malloc(sizeof(BTNode));
	assert(pNewNode);
	pNewNode->_data = data;
	pNewNode->_pLeft = NULL;
	pNewNode->_pRight = NULL;
	
	return pNewNode;
}

void _CreateBinTree(pBTNode *pRoot, BTDataType array[], int size, int *index, BTDataType invalid)	//前序遍历顺序来创建
{
	assert(index);		
	assert(size > 0);
	if ( *index < size && invalid != array[*index]) //索引边界,确保当前位置为有效值
	{
		/* 创建根节点 */
		*pRoot = BTBuyNode(array[*index]);
		//索引++取下一个位置的值,递归的去创建左子树
		++(*index);
		_CreateBinTree(&(*pRoot)->_pLeft, array, size, index, invalid);    //左子树
		
		++(*index);
		_CreateBinTree(&(*pRoot)->_pRight, array, size, index,invalid);   //右子树
	}
}

void CreateBinTree(pBTNode* pRoot, BTDataType array[], int size, BTDataType invalid)
{
	int index = 0;	//每次递归需将索引值进行更新,即传地址
	_CreateBinTree(pRoot, array, size, &index, invalid);//辅助函数递归的实现创建左右子树
}

二叉树的遍历递归版:

先序:先访问当前结点,在递归的访问左子树,然后是右子树

void PreOrder(pBTNode pRoot)	//前序递归  根---左----右
{
	if (pRoot)
	{
		printf("%c ", pRoot->_data);	
		PreOrder(pRoot->_pLeft);
		PreOrder(pRoot->_pRight);
	}
}

中序:先递归的访问左子树,在访问当前结点,然后是右子树

void InOrder(pBTNode pRoot)		//中序递归   左----根----右
{
	if (pRoot)
	{
		InOrder(pRoot->_pLeft);
		printf("%c ", pRoot->_data);
		InOrder(pRoot->_pRight);
	}
}

后序:先递归的访问左子树,然后是右子树, 最后访问当前结点

void PostOrder(pBTNode pRoot)		//后序递归    左-----右-----根
{
	if (pRoot)
	{
		PostOrder(pRoot->_pLeft);
		PostOrder(pRoot->_pRight);
		printf("%c ", pRoot->_data);
	}
}

二叉树的遍历非递归版:借助栈,队列

先序遍历:利用栈。

法1:先将根节点入栈,然后访问当前的栈顶元素并标记(在栈中对节点进行访问),然后将栈顶元素出栈,再保存出栈元素的右子树,再是左子树(先进后出)。当栈为空则说明左右子树均已遍历完成.

法2:先将根节点入栈,然后标记根节点,后将根节点出栈,从标记结点开始,先访问标记节点,然后将标记结点的右子树入栈,然后一直访问标记结点左子树,直到左子树为空,再从栈中取出右子树进行访问(在栈外依次访问结点,栈只用来保右子树)。当栈为空的时候,说明右子树也访问完毕。遍历完成。

void PreOrderNor(pBTNode pRoot)
{
#if 0		//法1
	Stack s;
	pBTNode pTop = NULL;
	if (NULL == pRoot)
		return;
	InitStack(&s);
	
	PushStack(&s, pRoot);		//先压栈根节点
	while (!StackEmpty(&s))
	{
		pTop = TopStack(&s);	//遍历当前节点
		printf("%c ", pTop->_data);
		PopStack(&s);		//将当前节点出栈

		if (NULL != pTop->_pRight)			//先压栈右子树
			PushStack(&s, pTop->_pRight);
		if (NULL != pTop->_pLeft)			//压栈左子树
			PushStack(&s, pTop->_pLeft);
	}
	printf("\n");
#endif
//法2
	Stack s;
	pBTNode pCur = NULL;
	if (NULL == pRoot)
		return;
	InitStack(&s);
	PushStack(&s, pRoot);
	while (!StackEmpty(&s))		
	{
		pCur = TopStack(&s);	//保存栈顶
		PopStack(&s);			//出栈
		while (pCur)
		{
			printf("%c ", pCur->_data);				//遍历当前结点
			if (pCur->_pRight)						//保存当前节点的右子树
				PushStack(&s, pCur->_pRight);
			pCur = pCur->_pLeft;
		}
	}
	printf("\n");
}
中序遍历:利用栈

先标记根节点,依次将标记结点的左子树入栈,然后取出栈顶结点标记,遍历后出栈,循环的处理当前结点的右子树。,当标记结点为空,说明当前结点的右子树为空,不需要入栈,如果栈也为空,说明树中序遍历完成。

void InOrderNor(pBTNode pRoot)
{
	pBTNode pCur = NULL;
	Stack s;
	if (NULL == pRoot)
	{
		return;
	}
	InitStack(&s);
	pCur = pRoot;
	//pCur标记当前的结点,如果pCur为空说明该节点的左右子树都为空,不需入栈,此时需要检查栈的状态
	while (pCur || !StackEmpty(&s))
	{
		while (pCur)	//找当前树中最左侧的结点并保存所经路径中的所有节点
		{
			PushStack(&s, pCur);
			pCur = pCur->_pLeft;
		}
		pCur = TopStack(&s);
		printf("%c ", pCur->_data);
		PopStack(&s);
		//处理当前元素的右子树,回到循环开始,去找到右子树的最左侧的结点
		pCur = pCur->_pRight;
	}

}
后序遍历:利用栈

定义三个标记pc标记当前访问的结点 ,pp标记上一次访问过的结点, pt标记栈顶的结点。先标记根节点为当前节点,从当前结点开始,找到当前结点最左边的子树,并将路径上的结点一次压栈,取出栈顶元素,如果其右子树为空或是pp所标记的结点,则说明当前结点的右子树已经被访问,就访问栈顶结点并将栈顶结点标记为pp,然后出。当pc指向空且栈为空时,则说明遍历结束。

void PostOrderNor(pBTNode pRoot)
{
	pBTNode pCur = NULL;
	pBTNode pPre = NULL;	//标记上一次访问过的结点
	pBTNode pTop = NULL;	//标记栈顶结点
	Stack s;
	if (NULL == pRoot)
	{
		return;
	}
	InitStack(&s);
	pCur = pRoot;
	while (NULL != pCur || !StackEmpty(&s))		//pc指向空并且栈为空则说明树遍历结束
	{
		while (pCur)	//找到最左边的结点,路径上的元素压栈
		{
			PushStack(&s, pCur);
			pCur = pCur->_pLeft;
		}
		pTop = TopStack(&s);
		//栈顶结点没有右子树或者当前节点的右子树已经被访问过,就访问当前结点,出栈
		if (NULL == pTop->_pRight || pTop->_pRight == pPre)
		{
			printf("%c ", pTop->_data);
			pPre = pTop;			//将访问过的结点标记
			PopStack(&s);
		}
		else			//否则循环的访问其右子树
		{
			pCur = pTop->_pRight;
		}
	}
}
层序遍历:利用队列

先将根节点入队列,访问,然后保存根节点的左右子树,然后出队列。当队列为空,则说明遍历结束,最后销毁队列。

void LevelOrder(pBTNode pRoot)
{
	Queue q;
	pBTNode pHead =	NULL;
	if (pRoot == NULL)
	{
		return;
	}
	QueueInit(&q);
	QueuePush(&q, pRoot);		//先将根节点入队列

	while (!QueueEmpty(&q))		//队列为空则说明层序遍历结束
	{
		pHead = QueueFront(&q);		//取队头
		printf("%c  ", pHead->_data);
		if (pHead->_pLeft)		//保存左子树
		{
			QueuePush(&q, pHead->_pLeft);
		}
		if (pHead->_pRight)		//保存右子树
		{
			QueuePush(&q, pHead->_pRight);
		}
		QueuePop(&q);
	}
	printf("\n");
	QueueDestory(&q);		//队列需要销毁
}

二叉树的面试题:注意充分应用二叉的性质,和三种遍历方式

二叉树结点个数:1+左子树结点个数+右子树结点个数

int SizeBinTree(pBTNode pRoot)
{
	if (NULL == pRoot)
	{
		return 0;
	}
	return (1 + SizeBinTree(pRoot->_pLeft) + SizeBinTree(pRoot->_pRight));	
}

二叉树叶子节点个数:左子树叶子节点个数+右子树叶子节点个数

int GetLeafCount(pBTNode pRoot)
{
	if (NULL == pRoot)
	{
		return 0;
	}
	if (NULL == pRoot->_pLeft && NULL == pRoot->_pRight)
	{
		return 1;
	}
	return GetLeafCount(pRoot->_pLeft) + GetLeafCount(pRoot->_pRight);
}

第K层结点个数:左子树第k-1层+ 右子树第k-1层几点个数之和

int GetLevelNodeCount(pBTNode pRoot, int k)//第K层结点个数
{
	if (NULL == pRoot || k <= 0)
		return 0;
	if (k == 1)
	{
		return 1;
	}
	return GetLevelNodeCount(pRoot->_pLeft, k - 1) + GetLevelNodeCount(pRoot->_pRight, k - 1);
}

获取二叉树的高度:左子树高度和右子树中较大的+1

int Height(pBTNode pRoot)
{
	int LeftHeight = 0;
	int RightHeight = 0;
	if (NULL == pRoot)
	{
		return 0;
	}
	LeftHeight = Height(pRoot->_pLeft);
	RightHeight = Height(pRoot->_pRight);
	
	return LeftHeight > RightHeight ? LeftHeight + 1 : RightHeight + 1;
}

二叉树镜像:先序遍历的应用

递归:

void Mirror(pBTNode pRoot)			//先序
{
	if (pRoot)
	{
		pBTNode pTmp = pRoot->_pLeft;		//先交换当前节点的左右子树
		pRoot->_pLeft = pRoot->_pRight;
		pRoot->_pRight = pTmp;
		if (pRoot->_pLeft)			//递归的镜像当前节点的左右结点
			Mirror(pRoot->_pLeft);
		if (pRoot->_pRight)
			Mirror(pRoot->_pRight);
	}
}
非递归:利用队列

类似层序遍历,先将根节点入队列,标记根节点,出队列;交换标记结点的左右子树,然后将标记结点的左右子树入队列。当队列为空时,说明进项结束。

void Mirror_Nor(pBTNode pRoot)		//验证镜像成功----镜像两次
{
	Queue q;
	pBTNode pTmp = NULL;
	pBTNode pHead = NULL;	//标记队头元素
	if (NULL == pRoot)
	{
		return;
	}
	QueueInit(&q);
	QueuePush(&q, pRoot);		//将根节点入队列

	while(!QueueEmpty(&q))
	{
		pHead = QueueFront(&q);

		pTmp = pHead->_pLeft;
		pHead->_pLeft = pHead->_pRight;
		pHead->_pRight = pTmp;
		
		if (pHead->_pLeft)
			QueuePush(&q, pHead->_pLeft);
		if (pHead->_pRight)
			QueuePush(&q, pHead->_pRight);
		
		QueuePop(&q);
	}
	QueueDestory(&q);
}

在二叉树中查找值为data的结点:先序遍历的应用

pBTNode Find(pBTNode pRoot, BTDataType data)		//查找值为data的元素
{
	pBTNode pRet = NULL;
	if (NULL == pRoot)
	{
		return NULL;
	}
	if (data == pRoot->_data)
		return pRoot;
	if (pRet = Find(pRoot->_pLeft, data))	//递归的在左子树中查找
		return pRet;			//找到返回
	return Find(pRoot->_pRight, data);	//都不满足就返回在右子树中的查找结果

}

判断一个节点是否在二叉树中

int IsNodeInBinTree(pBTNode pRoot, pBTNode pNode)
{
	int flag = 0;
	if (NULL == pRoot || NULL == pNode)
		return 0;
	if (pRoot == pNode)
	{
		return 1;
	}
	if(flag = IsNodeInBinTree(pRoot->_pLeft, pNode))
		return flag;
	return IsNodeInBinTree(pRoot->_pRight, pNode);
}

获取当前节点的左、右孩子

pBTNode LeftChild(pBTNode pNode)
{

	return (NULL == pNode) ? NULL : pNode->_pLeft;
}

获取当前结点的双亲结点

pBTNode Parent(pBTNode pRoot, pBTNode pNode)
{
	pBTNode pLeft = NULL;
	pBTNode pRight = NULL;
	if (NULL == pRoot || NULL == pNode)
	{
		return NULL;
	}
	if (pRoot == pNode)		//根节点双亲认为NULL
	{
		return NULL;
	}
	if (pNode == pRoot->_pLeft || pNode == pRoot->_pRight)
	{
		return pRoot;
	}
	pLeft = Parent(pRoot->_pLeft, pNode);
	pRight = Parent(pRoot->_pRight, pNode);
	return pLeft != NULL ? pLeft : pRight;
}

判断一棵树是否为完全二叉树:利用队列

最关键的步骤是找到特殊的结点(该节点只有左子树,或者该节点既没有左子树又没有右子树)。从该特殊结点开始,之后的结点都不能有子树。

int IsCompleteBinTree(pBTNode pRoot)		//判断完全二叉树, 借助队列
{
	Queue q;
	pBTNode pHead = NULL;
	int flag = 0;					//标记找到的特殊结点(从该特殊结点开始后面的节点不能右左右子树)
	if (NULL == pRoot)
	{
		return 1;
	}
	QueueInit(&q);
	QueuePush(&q, pRoot);
	//
	while (!QueueEmpty(&q))
	{
		pHead = QueueFront(&q);
		if (1 == flag)				//该节点为特殊结点后一个节点
		{
			if (pHead->_pLeft || pHead->_pRight)
			return 0;
		}
		else
		{
			if (pHead->_pLeft && pHead->_pRight)
			{
				QueuePush(&q, pHead->_pLeft);
				QueuePush(&q, pHead->_pRight);
				flag = 0;
			}
			else if (pHead->_pLeft)			//特殊结点
			{
				QueuePush(&q, pHead->_pLeft);
				flag = 1;
			}
			else if (pHead->_pRight)		//只有右孩子
				return 0;
			else					//没有左右子树(特殊结点)
				flag = 1;
		}
		QueuePop(&q);
	}
	return 1;
}

由先序遍历和中序遍历的结果还原一个二叉树:

遍历先序序列,在中序序列中确定先序遍历的当前元素在树中到底是左子树还是右子树。

int FindInOrder(BTDataType array[], int left, int right, BTDataType to_find)	//在数组中查找结点
{
	int i = left;
	for (; i < right; i++)
	{
		if (to_find == array[i])
		{
			return i;
		}
	}
	return -1;	//不能返回0!		
}


pBTNode _ReBuildTree(BTDataType pre_order[], int pre_order_size, int* index,
	BTDataType in_order[], int in_order_left, int in_order_right)
{
	assert(NULL != index);
	int IdxCurInInOrder = 0;			//当前结点在中序遍历结果中的位置
	if (in_order_left >= in_order_right)//说明中序遍历序列为空,即当前树没有左子树
	{
		return NULL;
	}
	if (*index >= pre_order_size)		//说明先序遍历结束,树还原结束
	{
		return NULL;
	}
	//取先序遍历的值创建新的结点
	pBTNode NewNode = BTBuyNode(pre_order[*index]);

	IdxCurInInOrder = FindInOrder(in_order,in_order_left, in_order_right, pre_order[*index]);	//在中序序列中找到先序序列元素的位置
	assert(IdxCurInInOrder != -1);		//找不到当前的结点,说明中序遍历结果传错了
	//必须先查找中序遍历的区间在进行index的自增
	++(*index);
	
	NewNode->_pLeft =  _ReBuildTree(pre_order, pre_order_size, index, in_order, in_order_left, IdxCurInInOrder);	//递归的处理当前结点的左子树
	NewNode->_pRight = _ReBuildTree(pre_order, pre_order_size, index, in_order, IdxCurInInOrder + 1, in_order_right);	//递归的处理当前结点的右子树
	return NewNode;
}

pBTNode ReBuildTree(BTDataType pre_order[], int pre_order_size,
	BTDataType in_order[], int in_order_size)		//由先序遍历和中序遍历的结果还原一个二叉树
{
	int index = 0;		//先序遍历的当前下标
	pBTNode NewNode = _ReBuildTree(pre_order, pre_order_size, &index, in_order, 0, in_order_size);	//辅助函数实现递归
	return NewNode;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值