二叉树的面试题(一)

在本篇博客中,访问节点,都是打印节点的数值,但是访问结点并不一定是打印数值,也可以是其他操作。

  // 本着学习的心,有错误 请指正。 还有部分题没有写,改天写

二叉树的特点:(1) 每个结点的度都大于2。

 (2) 每个结点的孩子结点次序不能任意颠倒,即有左右孩子之分。

二叉树的性质:

(1)若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1)个结点。

(2)若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (2^k)-1 。

(3)对任意一棵二叉树T,若终端节点数为n0,而其度数为2的节点树为n2,则n0 = n2+1 。

(4)具有n个结点的完全二叉树的深度为 lgN+1。

性质经常要用到,最好能记住,

先给出二叉树节点的定义:

template<class T>
struct BinaryTreeNode
{
	BinaryTreeNode(const T& data)
	:_data(data)
	, _pLeftChild(NULL)
	, _pRightChild(NULL)
	{}

	T _data;
	BinaryTreeNode<T>* _pLeftChild;
	BinaryTreeNode<T>* _pRightChild;
};

常见的题目: 还有两个题目求两个节点的最低公共祖先和节点的最大距离

1   创建二叉树

2 前序 中序 后序遍历(递归和非递归)

3层序遍历

4求二叉树的深度

5求二叉树中叶子结点的个数

6 求二叉树中节点的个数

7求二叉树第k层的节点个数

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


之后的面试题在下篇博客:二叉树面试题(二)


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

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

11判断二叉树是不是平衡二叉

12 判断一颗二叉树是否为完全二叉树

13 求二叉树的镜像

14 将二叉查找树变为有序的双向链表


详细解答

1创建二叉树

 思路:已前序遍历的特点来创建二叉树,如果没有左右孩子就用"#"来表示

 注意:递归调用时_CreatBinaryTree()的参数pRoot 和index数组下标为引用,

	BinaryTree(const T array[], size_t size)
		:_pRoot(NULL)
	{
		size_t index = 0;
		_CreatBinaryTree(_pRoot, array, size, index);
	}
	void _CreatBinaryTree(BinaryTreeNode<T>*& pRoot,const T array[], size_t size, size_t& index) 
	{ 
		if (index < size && '#' != array[index])
		{
			pRoot = new BinaryTreeNode<T>(array[index]);
			_CreatBinaryTree(pRoot->_pLeftChild,array,size,++index);
			_CreatBinaryTree(pRoot->_pRightChild,array,size,++index);
		}
	}

2 前序,中序,后序遍历(递归和非递归) //


(1)前序递归遍历:先访问根节点,再访问左右子树

	void _PreOreder(BinaryTreeNode<T>* proot) //前序遍历
	{
		if (proot)
		{
			cout << proot->_data << " ";
			_PreOreder(proot->_pLeftChild);
			_PreOreder(proot->_pRightChild);
		}
	}

 前序非递归遍历:基于栈的递归消除,实现遍历,栈是后进先出,先存放右孩子在存放左孩子

 思路: 1从根节点出发,根节点不为空时入栈,只有栈不为空循环进行下面操作

     2访问当前栈顶,出栈,右孩子存放入栈,左孩子存在入栈

	void PreOrder_Nor() //非递归
	//在栈中存放(后进先出) 先存放右孩子,再存放左孩子
	{
		cout << "前序遍历" << endl;
		if (_pRoot == NULL)
			return;
		stack<BinaryTreeNode<T>*> s;
		s.push(_pRoot);
		while (!s.empty())
		{
			BinaryTreeNode<T>* pCur = s.top();
			cout << pCur->_data << " "; //访问当前节点
			s.pop(); 
			if (pCur->_pRightChild)
				s.push(pCur->_pRightChild);
			if (pCur->_pLeftChild)
				s.push(pCur->_pLeftChild);
		}
	}


(2)中序递归遍历:先访问左孩子,再访问根节点,然后访问右孩子

	void _InOrder(BinaryTreeNode<T>* proot) //中序遍历
	{
		if (proot)
		{			
			_InOrder(proot->_pLeftChild);
			cout << proot->_data << " "; //访问根节点
			_InOrder(proot->_pRightChild);
		}
	}

中序非递归遍历:从根节点开始,只有当前节点存在或者栈不为空,则重复下面的工作

   1如果当前结点存在,则进栈并遍历左子树

   2否则退栈并访问,然后遍历右子树

	void InOrder_Nor() //非递归
	{
		cout << "中序遍历" << endl;
		if (NULL == _pRoot)
			return;


		BinaryTreeNode<T>* pCur = _pRoot;
		stack<BinaryTreeNode<T>*> s;
		while (NULL != pCur || !s.empty())
		{
			while (pCur) //存放节点,找到最左边的节点
			{
				s.push(pCur);
				pCur = pCur->_pLeftChild;
			}
			
			
			BinaryTreeNode<T>* pTop = s.top();
			cout << pTop->_data << " ";
			pCur = pTop->_pRightChild; //再找当前节点的右子树的最左边
			s.pop();
					
		}
		cout << endl;
	}


(3)后序递归遍历:先访问左子树,再访问右子树,最后访问根节点

	void _PostOrder(BinaryTreeNode<T>* proot)  //后续遍历
	{
		if (proot)
		{
			_PostOrder(proot->_pLeftChild);
			_PostOrder(proot->_pRightChild);
			cout << proot->_data << " "; 
		}
	}

后序非递归遍历:由于后序遍历是LRD,要求左右子树都访问完后,最后访问根节点。

判别是否应该访问当前栈顶节点p时,有两种情况:①p无右孩子,②p的右孩子是刚被访问过的节点。

除这两种情况外,均不能访问根,而是继续进入右子树中。

思路:①从当前节点开始,进栈并遍历左子树,直到左子树为空。

   ②如果栈顶节点的右子树为空或者栈顶节点的右孩子刚被访问过的结点,则退栈并访问,

   ③否则遍历右子树

  

	void PostOrder_Nor() //非递归
	// 存放到栈中:先找到树的最左边,
	{
		cout << "后序遍历" << endl;
		if (NULL == _pRoot)
			return;
		BinaryTreeNode<T> *pCur = _pRoot;
		BinaryTreeNode<T>* pPre = NULL; //保存已访问过的右子树
		stack<BinaryTreeNode<T>*> s;
		while (NULL != pCur || !s.empty())
		{
			while (pCur) //遍历左子树
			{
				s.push(pCur);
				pCur = pCur->_pLeftChild;
			}
			BinaryTreeNode<T>* pTop = s.top();
			if (NULL == pTop->_pRightChild || pTop->_pRightChild == pPre) //判断右子树是否为空或者访问过
			{
				cout << pTop->_data << " ";
				pPre = pTop;//保存已访问过的结点
				s.pop();
			}
			else
			{
				pCur = pTop->_pRightChild;
			}
		}
	}


3层序遍历二叉树:存放在队列中(先进先出) 先存放根节点,再存放左孩子,再存放右孩子

 

	void LevelOrder() //层序遍历
		//存放在队列中:先存放根节点,再存放左孩子,再存放右孩子
	{
		cout << "层次遍历" << endl;
		if (NULL == _pRoot)
			return;
		queue<BinaryTreeNode<T> *> q;
		if (_pRoot)
			q.push(_pRoot);
		while (!q.empty())
		{
			BinaryTreeNode<T>* pCur = q.front();
			cout << pCur->_data << " ";
			q.pop();	
			if (pCur->_pLeftChild)
				q.push(pCur->_pLeftChild);
			if (pCur->_pRightChild)
				q.push(pCur->_pRightChild);
		}	
	}


 4求二叉树的深度: 

 思路:①若bt为空,则高度为0

    ②若bt为空,则高度为左右子树高度的最大值加1.

	size_t _Height(BinaryTreeNode<T>* pRoot)
	{
		if (NULL == pRoot)
			return 0;
		if (NULL == pRoot->_pLeftChild && NULL == pRoot->_pRightChild)
			return 1;
		size_t LHeigt = _Height(pRoot->_pLeftChild);
		size_t RHeight = _Height(pRoot->_pRightChild);
		return (LHeigt > RHeight) ? (LHeigt + 1) : (RHeight + 1);
	}


5求二叉树中叶子结点的个数

有两种思路:采用遍历算法,只需将访问操作变为判断是否为叶子结点以及统计即可,

      采用递归算法,如果空树返回0,如果只有一个节点(根节点)返回1,否则为左右子树的叶子结点树之和

 ①后序遍历统计叶子结点个数

  //注意LeafCount为保存叶子结点的全局变量,调用前初始化为0;

	void _GetLeafNodeNum2(BinaryTreeNode<T>* pRoot)
		//后序遍历统计叶子结点
	{
		if (pRoot != NULL)
		{
			_GetLeafNodeNum2(pRoot->_pLeftChild);
			_GetLeafNodeNum2(pRoot->_pRightChild);
			if (pRoot->_pLeftChild == NULL && pRoot->_pRightChild == NULL)
				leftCountt++;
		}
	}


②//递归调用

	size_t _GetLeafNodeNum(BinaryTreeNode<T>* pRoot)
	// 采用递归算法,如果空树返回0,如果只有一个节点(根节点)返回1,否则为左右子树的叶子结点树之和
	{
		size_t leafCount = 0;
		if (pRoot == NULL)
			leafCount = 0;
		else if (pRoot->_pLeftChild == NULL && pRoot->_pRightChild == NULL)
			leafCount = 1;
		else
			leafCount = _GetLeafNodeNum(pRoot->_pLeftChild) + _GetLeafNodeNum(pRoot->_pRightChild);
		return leafCount;
	}


6求二叉树中节点的个数

递归解法:
(1)如果二叉树为空,节点个数为0
(2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1

	size_t _GetNodeNum(BinaryTreeNode<T>* pRoot)
	{
		if (pRoot == NULL)
			return 0;
		else
			return _GetNodeNum(pRoot->_pLeftChild) + _GetNodeNum(pRoot->_pRightChild) + 1;
	}

7求二叉树中第K层的节点个数

递归解法:
(1)如果二叉树为空或者k<1返回0
(2)如果二叉树不为空并且k==1,返回1
(3)如果二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和

	size_t _GetKthLeverNodeNum(BinaryTreeNode<T>* pRoot,size_t k)
	{
		if (pRoot == NULL || k == 0)
			return 0;
		if (k == 1 && pRoot != NULL)
			return 1;
		int leftNum = _GetKthLeverNodeNum(pRoot->_pLeftChild,k-1);
		int rightNum = _GetKthLeverNodeNum(pRoot->_pRightChild,k-1);
		return leftNum + rightNum;
	}

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

 不考虑数据内容。结构相同意味着对应的左子树和对应的右子树都结构相同。
 递归解法:
(1)如果两棵二叉树都为空,返回真
(2)如果两棵二叉树一棵为空,另一棵不为空,返回假
(3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假

	bool StructureCmp(BinaryTreeNode *pRoot1, BinaryTreeNode *pRoot2)
	{
		if (pRoot1 == NULL && pRoot2 == NULL) // 都为空,返回真  
			return true;
		else if (pRoot1 == NULL || pRoot2 == NULL) // 有一个为空,一个不为空,返回假  
			return false;
		bool resultLeft = StructureCmp(pRoot1->m_pLeft, pRoot2->m_pLeft); // 比较对应左子树   
		bool resultRight = StructureCmp(pRoot1->m_pRight, pRoot2->m_pRight); // 比较对应右子树  
		return (resultLeft && resultRight);
	}


其他面试题在下篇博客:二叉树面试题(二)


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值