数据结构二叉树常见面试题

题目:

1. 求二叉树中的节点个数
2. 求二叉树的深度
3. 求二叉树中叶子节点的个数
4. 求二叉树第K层的节点个数
5. 前序遍历,中序遍历,后序遍历(递归版本,非递归版本)
6. 分层遍历二叉树(按层次从上往下,从左往右)
7. 将二叉查找树变为有序的双向链表
8. 判断两棵二叉树是否结构相同
9. 判断二叉树是不是平衡二叉树(AVL树)
10. 求二叉树的镜像
11. 求二叉树中两个节点的最低公共祖先节点
12. 求二叉树中节点之间的最大距离
13. 由前序遍历序列和中序遍历序列重建二叉树
14. 由后序遍历序列和中序遍历序列重建二叉树

准备工作:先定义二叉树的节点

//先构建二叉树节点
typedef struct BTreeNode
{
	int val;
	BTreeNode* pLeft;
	BTreeNode* pRight;
}Node;

typedef BTreeNode*  PNode;


1. 求二叉树中的节点个数

思路:1.如果二叉树为空,节点个数为0
     2.如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1。 (这个1就是表示根节点)
int GetNodeNum(PNode pRoot)
{
	if (pRoot == nullptr)
		return 0;
	return 1 + GetNodeNum(pRoot->pLeft) + GetNodeNum(pRoot->pRight);
	//这种写法是一种从上至下的写法,遇到就会要加1,适合求总个数
}

2.求二叉树的深度

/*
	思路:二叉树的深度等于左子树的深度 与 右子树深度中较大的值
*/
#define MAX(a,b) (a)>(b)?(a):(b)
int GetHeightOfTree(PNode pRoot)
{
	if (pRoot == nullptr)
		return 0;
	int leftHeight =  GetHeightOfTree(pRoot->pLeft);
	int rightHeight =  GetHeightOfTree(pRoot->pRight);
	int height = MAX(leftHeight , rightHeight)+1;
	//这种写法是一种从下至上的写法,先找到叶子结点,从叶子结点向上累加深度
	return height;
}

3.求二叉树中叶子节点的个数

思路:1.终止条件: 空树 或者 当前节点的左右孩子都是nullptr
	 2.递推公式:
	      二叉树的叶子结点个数 = 左子树叶子结点个数 + 右子树叶子结点个数   

int GetLeafNum(PNode pRoot)
{
	if (pRoot == nullptr)
		return 0;
	if (pRoot->pLeft == nullptr && pRoot->pRight == nullptr)
		return 1;
	return GetLeafNum(pRoot->pLeft) + GetLeafNum(pRoot->pRight);
}

4.求二叉树第K层的节点个数

思路:  每向下一层,高度减1,当k减为1时,说明来到第k层
	递推公式:左子树第k层结点个数 + 右子树第k层结点个数
    终止条件:空树,返回0; k==1,返回1,表示只有一个节点,高度为1
int GetNumOfKlevel(PNode pRoot, int k)
{
	if (pRoot == nullptr || k < 1)
		return 0;
	if (k == 1)
		return 1;
	return GetNumOfKlevel(pRoot->pLeft, k - 1) + GetNumOfKlevel(pRoot->pRight,k-1);
}

5.前序遍历,中序遍历,后序遍历
递归版本:

//递归版本
void PreOrder(PNode pRoot)  //前序遍历  根 左 右
{
	if (pRoot == nullptr)
		return;
	cout << pRoot->val << endl; //打印根节点
	PreOrder(pRoot->pLeft); //打印左子树
	PreOrder(pRoot->pRight);  //打印右子树
}

void InOrder(PNode pRoot) //中序遍历  左 根 右
{
	if (pRoot == nullptr)
		return;
	InOrder(pRoot->pLeft);   //先打印左子树
	cout << pRoot->val << endl;  //打印根节点
	InOrder(pRoot->pRight);  //打印右子树
}

void PostOrder(PNode pRoot) //后续遍历  左 右 根
{
	if (pRoot == nullptr)
		return;
	PostOrder(pRoot->pLeft);  //打印左子树
	PostOrder(pRoot->pRight);  //打印右子树
	cout << pRoot->val << endl; //打印根节点
}

非递归版本:
大体思路:需要使用一个栈来存放访问过的节点

非递归前序遍历:
从整个二叉树的根节点开始,访问根节点,并且将根节点入栈,判断根节点的
左孩子是否为空。如果左孩子不为空,就持续入栈左孩子,如果左孩子为空,就取出
栈顶节点,然后将栈顶节点的右孩子入栈。重复操作,知道当前节点为空并且栈为空,遍历结束。

非递归中序遍历:
左根右,根节点如果不为空,就将根节点先压入栈中,之后一直将根的左孩子压入栈中。
如果根的左孩子为空,说明此时根是二叉树最左边的节点,打印根节点,然后将根出栈,将根的右孩子压入栈中。
重复操作,直到当前节点为空,并且栈也为空,结束。

非递归后序遍历:
首先将整棵二叉树的根节点入栈,取栈顶节点,若栈顶节点不存在左右孩子,或者栈顶元素存在左右孩子,
但是左右孩子都被访问过了,则访问栈顶节点,并将栈顶节点从栈中弹出。若非上述两种情况,
则将栈顶的右孩子和左孩子依次入栈。重复扇面操作,直到栈为空,遍历结束。

//非递归版本
/*
	非递归前序遍历:
	   从整个二叉树的根节点开始,访问根节点,并且将根节点入栈,判断根节点的
	   左孩子是否为空。如果左孩子不为空,就持续入栈左孩子,如果左孩子为空,就取出
	   栈顶节点,然后将栈顶节点的右孩子入栈。重复操作,知道当前节点为空并且栈为空,遍历结束。
*/

void PreOrderNotRecursive(PNode pRoot)
{
	if (pRoot == nullptr)
		return;
	stack<PNode> s;
	PNode node = pRoot;
	PNode top;
	while (node != nullptr || !s.empty())
	{
		while (node != nullptr)
		{
			cout << node->val << endl;   //先打印当前的根的值
			s.push(node);       
			node = node->pLeft;    //不断的将根的最左边的单支树部分压入栈中
		}

		top = s.top();   //该将栈顶元素的右孩子压入栈中了
		s.pop();     //原来的栈顶元素已经没有用处了(因为栈顶已经打印了,栈顶左孩子也访问过了,现在把右孩子作为栈顶元素)
		node = top->pRight;
	}
}

/*
	非递归中序遍历:
		左根右,根节点如果不为空,就将根节点先压入栈中,之后一直将根的左孩子压入栈中。
		如果根的左孩子为空,说明此时根是二叉树最左边的节点,打印根节点,然后将根出栈,将根的右孩子压入栈中。
		重复操作,直到当前节点为空,并且栈也为空,结束。
*/
void InOrderNotRecursive(PNode pRoot)
{
	if (pRoot == nullptr)
		return;
	stack<PNode> s;
	PNode pCur = pRoot;
	PNode top;
	while (pCur != nullptr || !s.empty())
	{
		while (pCur != nullptr)
		{
			s.push(pCur);
			pCur = pCur->pLeft;
		}

		top = s.top();
		cout << top->val << endl;
		s.pop();
		pCur = top->pRight;
	}
}

/*
	非递归后序遍历:
		首先将整棵二叉树的根节点入栈,取栈顶节点,若栈顶节点不存在左右孩子,或者栈顶元素存在左右孩子,
		但是左右孩子都被访问过了,则访问栈顶节点,并将栈顶节点从栈中弹出。若非上述两种情况,
		则将栈顶的右孩子和左孩子依次入栈。重复扇面操作,直到栈为空,遍历结束。
*/

void PostOrderNotRecursive(PNode pRoot)
{
	if (pRoot == nullptr)
		return;
	stack<PNode> s;
	PNode pCur = pRoot;
	PNode top;
	PNode flagNode = nullptr;

	while (pCur != nullptr || !s.empty())
	{
		while (pCur != nullptr)
		{
			s.push(pCur);
			pCur = pCur->pLeft;
		}

		top = s.top();
		if (top->pRight == nullptr || top->pRight == flagNode ) //难点分析就在这一步:首先条件是右孩子不存在(top->pRight == nullptr),
			                                                     //其次是右孩子存在,但是访问过了,访问过的右孩子就赋值给flagNode(top->pRight == flagNode )
		{
			cout << top->val << endl;
			flagNode = top;
			s.pop();
		}
		else
		{
			pCur = pCur->pRight;
		}
	}
}

6.层序遍历

层序思路:先遇到的节点要先访问,所以使用队列使用一个队列保存结点,访问队首元素,队首元素出队列,在出队列之前将队首元素的左右孩子入队 当队列为空时,层序遍历结束


#include <queue>
void LevelTraverse(PNode pRoot)
{
	if (pRoot == nullptr)
		return;
	queue<PNode> q;
	PNode top;
	q.push(pRoot);
	while (!q.empty())
	{
		top = q.front();
		cout << top->val << endl;
		if (top->pLeft != nullptr)
			q. push(top->pLeft);
		if (top->pRight != nullptr)
			q.push(top->pRight);

		q.pop();
	}
	return;
}

/7.将二叉查找树变为有序的双向链表

 思路:
	1.如果二叉查找树为空,则不需要转换,对应双向链表的第一个节点是NULL,最后一个节点也是NULL
	2.如果二叉查找树不为空:
		如果左子树为空,对应双向有序链表的第一个节点是根节点,左边不需要其他操作;		
		如果左子树不为空,转换左子树,二叉查找树对应双向有序链表的第一个节点就是左子树转换成双向有序链表的第一个节点,
		同时将根节点和左子树转换后的双向有序链表中的最后一个节点连接;
		如果右子树为空,对应双向有序链表的最后一个节点是根节点,右边不需要其他操作;

		如果右子树不为空,对应双向有序链表的最后一个节点就是右子树转换后双向有序链表的最后一个节点,
		同时将根节点和右子树转换后的双向有序链表的第一个节点连接。

用到的参数:
	pRoot:二叉查找树根节点指针
	pFirstNode:转换后双向有序链表的第一个节点指针
	pLastNode:转换后双向有序链表的最后一个节点
void Convert(PNode pRoot, PNode& pFirstNode, PNode& pLastNode)
{
	PNode pFirstLeft, pLastLeft, pFirstRight, pLastRight;
	if (pRoot == nullptr)
	{ 
		//二叉搜索树为空,则对应双向链表的第一个节点和最后一个节点都是nullptr
		pFirstNode = nullptr;
		pLastNode = nullptr;
		return;
	}
	
	if (pRoot->pLeft == nullptr)
	{
		//如果左子树为空,对应双向有序链表的第一个节点是根节点
		pFirstNode = pRoot;
	}
	else
	{
		Convert(pRoot->pLeft,pFirstLeft,pLastLeft);

		//二叉查找树对应双向有序链表的第一个节点就是左子树转换后双向有序链表的第一个节点
		pFirstNode = pFirstLeft;

		//将根节点和左子树转换后的双向有序链表的最后一个节点连接
		pRoot->pLeft = pLastLeft;
		pLastLeft->pRight = pRoot;
	}

	if (pRoot->pRight == nullptr)
	{
		//对应双向有序链表的最后一个节点是根节点
		pLastNode = pRoot;
	}
	else
	{
		Convert(pRoot->pRight, pFirstRight, pLastRight);
		//对应双向有序链表的最后一个节点就是右子树转换后双向有序链表的最后一个节点
		pLastNode = pLastRight;

		//将根节点和右子树转换后的双向有序链表的第一个节点连接
		pRoot->pRight = pFirstRight;
		pFirstRight->pLeft = pRoot;
	}
	return;
}

8. 判断两棵二叉树是否结构相同


思路:不考虑数据内容,结构相同意味着对应的左子树和对应的右子树都结构相同,使用递归.		 

 递归公式:两棵树的左子树是否相同,两棵树的右子树是否相同
 结束条件:
     1.如果两棵二叉树都为空,返回真
     2.如果两棵二叉树一棵为空,另一棵不为空,返回假。
     3.如果两棵二叉树都不为空,如果对应的左子树和右子树结构相同,就返回真,其他返回假。

bool StructureCmp(PNode pRoot1, PNode pRoot2)
{
	if (pRoot1 == nullptr && pRoot2 == nullptr) //都为空,返回真
		return true;
	else if (pRoot1 == nullptr || pRoot2 == nullptr) //有一个为空,一个不为空,返回假
	{
		return false;
	}
	bool resultLeft = StructureCmp(pRoot1->pLeft, pRoot2->pLeft);
	bool resultRight = StructureCmp(pRoot1->pRight,pRoot2->pRight);

	return (resultLeft && resultRight);
}


9.判断二叉树是不是平衡二叉树


思路:递归解法:
	  1.如果二叉树为空,返回真
      2.如果二叉树不为空,如果左子树和右子树都是AVL树,并且左子树和右子树高度差不大于1,返回真,其他返回假

#include <algorithm> //max调用
#include <math.h>  //abs 调用
bool IsAVL(PNode pRoot, int& height) //传参传的是引用,目的是height为带出参数,相当于有两个返回值
{
	if (pRoot == nullptr)  //空树,返回真
	{
		height = 0;
		return true;
	}

	int heightLeft;
	bool resultLeft = IsAVL(pRoot->pLeft,heightLeft);
	int heightRight;
	bool resultRight = IsAVL(pRoot->pRight,heightRight);

	if (resultLeft && resultRight && abs(heightLeft - heightRight) <= 1)
	{
		//左子树和右子树都是AVL都是AVL,并且高度差不大于1,返回真
		height = max(heightLeft, heightRight) + 1;
		return true;
	}
	else
	{
		height = max(heightLeft, heightRight) + 1;
		return false;
	}
}


10.求二叉树的镜像
镜像翻转过程:在这里插入图片描述

思路: 
	递归解法---所谓镜像,就相当于从镜子中看一棵树,这个数和原来的树相反的
			1.如果二叉树为空,返回真
			2.如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树

PNode Mirror(PNode pRoot)
{
	if (pRoot == nullptr)
		return nullptr;
	PNode pTreeLeft = Mirror(pRoot->pLeft); //求左子树镜像
	PNode pTreeRight = Mirror(pRoot->pRight); //求右子树镜像

	//交换左子树和右子树
	pRoot->pLeft = pTreeRight;
	pRoot->pRight = pTreeLeft;
	return pRoot;
}

11. 求二叉树中两个节点的最低公共祖先节点

思路:递归方法
    1.如果两个节点分别在根节点的左子树和右子树,则返回根节点
    2.如果两个节点都在左子树,则递归处理左子树;如果两个节点都在右子树,则递归处理右子树

bool FindNode(PNode pRoot, PNode pNode)
{
	if (pRoot == nullptr || pNode == nullptr)
		return false;
	if (pRoot == pNode)
		return true;
	bool found = FindNode(pRoot->pLeft, pNode); //在左子树中查找

	if (found == true)
		return found;
	else
	{
		found = FindNode(pRoot->pRight, pNode);
	}
	return found;
}

PNode GetLastCommonParent(PNode pRoot, PNode pNode1, PNode pNode2)
{
	if (pRoot == nullptr)
		return nullptr;
	if (FindNode(pRoot->pLeft, pNode1))
	{
		if (FindNode(pRoot->pRight, pNode2))
		{
			//pNode1在左子树,pNode2在右子树
			return pRoot;
		}
		else
		{
			//两个节点都在左子树,需要递归查找左子树中的内容
			return GetLastCommonParent(pRoot->pLeft, pNode1, pNode2);
		}
	}
	else
	{
		//pNode1在右子树,pNode2在左子树
		if (FindNode(pRoot->pLeft, pNode2))
		{
			return pRoot;
		}
		else
		{
			//两个节点都在右子树,需要递归查找右子树中的内容
			return GetLastCommonParent(pRoot->pRight, pNode1, pNode2);
		}
	}
}


12.求二叉树中节点的最大距离,即求二叉树中相距最远的两个节点之间的距离。

思路:递归解法
	1.如果二叉树为空,返回0,同时记录左子树和右子树的深度,都为0
	2.如果二叉树不为空,最远距离要么就是左子树的最大距离,要么就是右子树中的最大距离,要么是
		左子树节点中到根节点的最大距离+右子树节点中到根节点的最大距离,同时记录左子树和右子树
		到根节点的最大距离。
		
int GetMaxDistance(PNode pRoot, int& maxLeft, int& maxRight)
{
	//maxLeft,左子树中节点距离根节点的最远距离
	//maxRight,右子树中的节点距离根节点的最远距离
	if (pRoot == nullptr)
	{
		maxLeft = 0;
		maxRight = 0;
		return 0;
	}
	int maxLL, maxLR, maxRR, maxRL;
	int maxDistLeft, maxDistRight; //左右子树中最长距离

	//二叉树不为空,到左子树中找最远距离
	if (pRoot->pLeft != nullptr)
	{
		maxDistLeft = GetMaxDistance(pRoot->pLeft, maxLL,maxLR);
		maxLeft = max(maxLL,maxLR) + 1;
	}
	else
	{
		maxDistLeft = 0;
		maxLeft = 0;
	}

	//二叉树不为空,到右子树中找最远距离
	if (pRoot->pRight != nullptr)
	{
		maxDistRight = GetMaxDistance(pRoot->pRight,maxRL,maxRR);
		maxRight = max(maxRL,maxRR) + 1;
	}
	else
	{
		maxDistRight = 0;
		maxRight = 0;
	}
	return max(max(maxDistLeft,maxDistRight), maxLeft+maxRight);
}

13. 由前序遍历序列和中序遍历序列重建二叉树


思路: 1.前序的第一个数据是二叉树的根节点,在中序数组中找到前序的第一个数据,
        中序数组前面的就是左子树内容,后面的就是右子树内容
	  2.使用递归,递归的停止条件是:区间内没有元素

int FindIndex(char arr[], int size, int value)
{
	for (int i = 0; i < size; i++)
	{
		if (arr[i] == value)
			return i;
	}
	return -1;
}

//还原二叉树,返回重建的二叉树的根节点
PNode ReBuildBTreeByPreOrderAndInOrder(char preOrder[], char inOrder[], int size)
{

	//终止条件
	if (size == 0)
		return nullptr;

	//递推
	char rootValue = preOrder[0];
	int leftSize = FindIndex(inOrder, size, rootValue);  //左子树中节点个数

	//根
	PNode root = new Node;
	root->val = preOrder[0];
	root->pLeft = root->pRight = nullptr;

	//左子树
	root->pLeft = ReBuildBTreeByPreOrderAndInOrder(preOrder+1, inOrder, leftSize);

	//右子树
	root->pRight = ReBuildBTreeByPreOrderAndInOrder(preOrder + leftSize + 1 , inOrder + leftSize + 1, size-1-leftSize);
	return root;
}


14. 由后序遍历序列和中序遍历序列重建二叉树

思路:递归
	1.后序遍历的数组最后一个元素是根,找到它在中序遍历数组中的位置,中
	  序前面的就是左子树节点个数,中序后面的就是右子树节点的个数
	2.递归的停止条件是:区间没有元素


PNode ReBuildBTreeByPreOrderAndInOrder(char postOrder[], char inOrder[], int size)
{
	//终止条件
	if (size == 0)
		return nullptr;

	char rootValue = postOrder[size-1];
	int leftSize = FindIndex(inOrder, size, rootValue);

	//根节点,先申请空间创建一个根节点
	PNode root = new Node;
	root->val = postOrder[size-1];
	root->pLeft = root->pRight = nullptr;

	//左子树
	root->pLeft = ReBuildBTreeByPreOrderAndInOrder(postOrder,inOrder, leftSize);

	//右子树
	root->pRight = ReBuildBTreeByPreOrderAndInOrder(postOrder + leftSize, inOrder + leftSize +1, size- leftSize- 1);

	return root;
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值