数据结构之二叉树面试题整理内容
【摘要】在数据结构中,二叉树是一种类似于树型的结构,常见的二叉树由递归和非递归两种构建。递归方式构建二叉树很方便,缺点就是递归的使用要考虑栈桢的问题,而非递归虽然复杂一些,但是却不用考虑。这篇文章,我会把我最近学到的一些关于二叉树的构建以及面试题很详尽的写下来,希望可以帮助到你们。(附:有些问题用递归和非递归都可以解决,因此我会写两种方法)
常见的面试题如下
1.递归创建二叉树
2.非递归创建二叉树
3.二叉树的三种遍历方法
4.求二叉树的结点个数
5.求一棵二叉树叶子结点的个数
6.求一棵二叉树第k层节点的个数
7.判断一棵二叉树是否是完全二叉树(两种方法)
8.在二叉树中查找元素
9.求二叉树的深度
10.层序遍历二叉树(借用栈实现)
一.递归创建二叉树
递归创建二叉树需要注意返回条件(直到根节点),我这里使用的是先序遍历进行递归的条件,所以是先创建根节点,然后是左孩子,然后是右孩子,递归走下去的条件就是找到根节点的左孩子结点,一直往下走,直到左孩子为空,然后再返回去找右孩子,如此反复,直到返回到根节点,一颗二叉树就创建好了。
代码
BTNode* CreateBTree(DataType *a,size_t *index,DataType invalid)//递归的思想进行树的创建
{ //用前序遍历的方法根左右进行,递归结束条件为左右为空就返回到根
BTNode *root=NULL; //函数栈桢(这个部分需要认真了解)在递归到那一层在进行返回之后栈桢就销毁了
if(a[index]!=invalid)
{
root=BuyBTNode(a[*index]);
root->left=CreateBTree(a,++*index,invalid);
root->right=CreateBTree(a,++*index,invalid);
}
return root;
二.非递归创建二叉树
非递归创建二叉树借助了栈的操作(先序遍历)
递归的思想是划分为子问题,进行非递归操作的时候也是将整体转化为一个个子问题。先将一个指针cur指向根节点,然后再将根节点入栈,然后将根节点指向左孩子结点,左孩子不为空就入栈,再将左孩子结点赋给cur,让cur指向它的左孩子结点,然后继续入栈,直到左子树的左孩子结点为空,就返回到根节点,再对右孩子结点进行入栈,直到右子树的右孩子也为空时,入栈结束,二叉树创建完成。
代码
void BTreePrevOrderNonR(Node *root)
{
BTNode* cur=root;
Stack s;
StackInit(&s);
while(cur || StackEmpty(&s)!=0)
{
while(cur)
{
printf("%d",cur->data);
StackPush(&s,cur);
cur=cur->_left;
}
top=StackTop(&s);
StackPop(&s);
cur=top->right;
}
}
三。二叉树的三种遍历方法(前序遍历,中序遍历,后序遍历)
(1)前序遍历遵循根左右的方式进行二叉树的遍历,因此先访问根结点,然后是左结点,最后是右结点
void BTreePreOrder(BTNode *root)
{
if(root==NULL)
return;
printf("%d",root->data);
BTreePrevOrder(root->left);
BTreePrevOrder(root->right);
}
(2)中序遍历是先访问左结点,然后是根节点,最后才是右结点
四。求二叉树的结点个数
1. 求二叉树的结点个数,用的是递归的思想。(第二种非递归)
1)如果二叉树是空就返回0
2)如果二叉树非空但是左右孩子结点为空就返回1
3)如果左右子树非空那就返回左树节点个数加上右子树结点个数
代码
size_t BTreeSize(BTNode *root)
{
if(root ==NULL)
return NULL;
return BTNodeSize(root->_left)+BTNodeSize(root->_right)+1;
}
2.非递归计算二叉树结点个数,其实和循环很类似,就是通过size++,进行结点个数的累积,直到整个二叉树遍历完得到的size就是结点个数
代码
size_t BTreeSize1(BTNode *root,size_t *pSize)//线程安全
{
if(root==NULL) //每个线程都有独立的栈桢
return NULL ;
++size;
BTreeSize1(root->_left,size_t *pSize);
BTreeSize1(root->_right,size_t *pSize);
}
五.求一棵树叶子结点的个数(这属于上一个问题的变形)
1)如果二叉树是空就返回0
2)如果二叉树非空但是左右孩子结点为空就返回1
3)如果左右子树非空那就返回左树叶子节点个数加上右子树叶子结点个数
代码
size_t BTreeLeafSize(BTNode *root)
{
if(root ==NULL)
return NULL;
if(root->_left==NULL &&root->_right==NULL)
return 1;
return BTreeLeafSize(root->_left)+BTreeLeafSize(root->right);
}
七.判断一棵树是否是完全二叉树
方法一
第一步:借助队列,从头结点开始入队列,每次根节点入队列之后就把左右孩子也入队列,然后再把根节点出队列。
第二步:根节点指向的左孩子重新作为根节点,将左孩子出队列,然后再把新的根节点的左右孩子入队列。
第三步:如果左右孩子都为空,那就给队列里面入NULL,返回条件为重新返回到根节点。
第四步:判断 如果是完全二叉树的话,队列里面应该存的都是空,相反则不是
代码
int IsCompleteBTree(BTNode *root)
{
Queue q;
QueueInit(&q);
if(root)
QueuePush(&q,root);
while(QueueEmpty(&q)!=0)
{
BTNode *front=QueueFront(&q);
QueuePop(&q);
if(front==NULL)
{
break;
}
else
{
QueuePush(&q,front->left);
QueuePush(&q,front->right);
}
}
while(QueueEmpty(&q)!=0)
{
BTNode*front=QueueFront(&q);
if(front)
{
return 0;
}
QueuePop(&q);
}
return 1;
}
2.置tag法
第一步:先判断二叉树是否非空,然后开始给栈中入数据,根据先序遍历的原则,先入根节点
第二步:入根节点的孩子节点,如果左右孩子都不为空,那就入栈,并且tag仍然为1,如果其中有一个空就把tag置为0
第三步:如果碰到tag为0但是二叉树中还有数据没有压栈,那么这棵树就不是完全二叉树,相反就是完全二叉树
代码
int IsCompleteBTree(BTNode *root)
{
Queue q;
QueueInit(&q);
if(root)
QueuePush(&q,root);
while(QueueEmpty(&q)!=0)
{
BTNode*front=QueueFront(&q);
QueuePop(&q);
if(front->left)
{
if(flag==0)
{
return 0;
}
QueuePush(front->left);
}
else
{
flag=0;
}
if(front->right)
{
if(flag==0)
{
return 0;
}
QueuePush(&q,front->right);
}
else
{
flag=0;
}
}
八.在二叉树中查找元素
在二叉树中查找元素还是用的递归的思路转化为子问题,
第一步:将一颗完整的二叉树看成是三个部分,根,左子树和右子树
第二步:利用先序遍历的思想进行左子树的遍历,如果没找到数据就在右子树中遍历,如果找到就返回1,否则返回0
代码
BTNode *BTreeFind(BTNode* root,DataType x)
{
Node*leftRet,rightRet;
if(root==NULL)
return NULL;
if(root->data==x)
return root;
leftRet=BTreeFind(root->left,x);
if(leftRet)
return leftRet;
rightRet=BTreeFind(root->right,x);
if(rightRet)
return rightRet;
return NULL;
}
void BTreeLevelOrder(BTNode *root) //层序遍历
{
Queue q;
QueueInit(&q);
if(root)
QueuePush(&q,root);
while(QueueEmpty(&q)!=0)
{
BTNode*front=QueueFront(&q);
printf("%d",front->data);
QueuePop(&q);
if(front->left)
QueuePush(&q,front->left);
if(front->right)
QueuePush(&q,front->right);
}
printf("\n");
}
九.求二叉树深度
求二叉树的深度其实也很简单,只需要在之前代码的基础上进行修改就可以了
思路:还是转化为子问题,要求二叉树的深度那就是求左子树的深度和右子树深度中较大的一个加1,而求左子树的深度又可以化为更小的左子树和右子树,层层递归,直到到达二叉树的最底部,然后开始返回值,每上升一层就加1,直到根节点,就可以得到深度了
代码
int BTreeDepth(BTNode *root)
{
if(root==NULL)
return 0;
size_t leftDepth=BTreeDepth(root->left);
size_t rightDepth=BTreeDepth(root->right);
return leftDepth>rightDepth?leftDepth+1:rightDepth+1;
}
十.层序遍历二叉树
层序遍历二叉树也需要借助队列的先进先出的原则,具体步骤如下
第一步:先判断二叉树是否非空,不是空的话,就将根节点入队列,然后再将根节点的双亲孩子节点也入队列,然后再将根节点出队列
第二步:分别将两个孩子作为根节点,将他们的孩子节点入队列,然后再把他们出队列,依次进行入队列和出队列的操作,这样按顺序访问到的数据就是一层一层访问的
代码
void BTreeLevelOrder(BTNode *root) //层序遍历
{
Queue q;
QueueInit(&q);
if(root)
QueuePush(&q,root);
while(QueueEmpty(&q)!=0)
{
BTNode*front=QueueFront(&q);
printf("%d",front->data);
QueuePop(&q);
if(front->left)
QueuePush(&q,front->left);
if(front->right)
QueuePush(&q,front->right);
}
printf("\n");
}
总结:二叉树的构建以及各种操作中大多数都可以同时使用递归和非递归,但是在使用它们的时候需要权衡好利弊,如果入栈太深,建议不要使用递归,这样时间上花费的代价很大,并且很有可能出现栈溢出的问题,但是作为一些小的问题,只要递归不是很深,递归还是比较方便的,这就要具体情况了。
注:我也是在自学中,如果文章中有纰漏欢迎大家指正,希望可以互相交流进步。