您的位置:首页 > 职场人生

面试常考之二叉树

2015-08-11 11:17 579 查看
二叉树在数据结构面试中的地位举足轻重,算得上是大公司面试必问,笔试必考;因为对二叉树的操作直接反应一个人的数据结构功底有多深厚,基础知识是否扎实。。。(一点废话),下面就二叉树的基本操作说一说二叉树的知识点,不对之处还请指正。

面试常考的几个操作:

1:二叉树的基本性质

2:递归建立二叉树

3:递归遍历二叉树(先序,中序,后序)

4:非递归遍历二叉树(先序,中序,后序)

5:求二叉树中的节点个数

6:求二叉树的深度

7:分层遍历二叉树

8:求二叉树第K层的节点个数

9:求二叉树的镜像

10:测试源码

下面就常用的算法给大家讲解这些二叉树的基本性质。

说明:

所有代码使用C语言编写,头信息,常量如下:

**
* 树的基本操作tree
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ERROR -1
#define OK 1
#define OVERFLOW 2
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 20

//最大队列长度
#define MAXQSIZE 100

typedef int Struct;
typedef char TElement;
typedef int Status;
int i = 0; //记录树初始化过程,数据的偏移
int j = 0; //记录树初始化过程,数据的偏移


二叉树结构体,以及栈、队列的结构体(层次遍历,非递归要用到)如下:

//二叉树结构体(二叉链表存储结构)
typedef struct BiNode{
TElement data;
struct BiNode *lchild,*rchild;
}BiTNode, *BiTree;

//栈表示(用于非递归)
typedef struct{
BiTree *base;
BiTree *top;
int stacksize;
}SqStack;

//使用队列,实现层次遍历
typedef struct QNode{
BiTree data;
struct QNode *next;
}QNode, *QueuePtr;

typedef struct{
QueuePtr front;
QueuePtr rear;
}LinkQueue;


1:二叉树的基本性质


一、二叉树第 i 层上最多有2i-1个节点

二、深度为 k 的二叉树,最多有2k - 1个节点

三、对于任意一颗二叉树T,如果其终端节点数为n1度数为2的节点数为n2 则 n1 = n2 + 1;

四、具有n个节点的完全二叉树深度为[ log2n] + 1;

2:递归建立二叉树

递归常见二叉树,先从左子树开始,arr 是调用传值过去的节点值,i 用于移动数组内的节点值

//创建二叉树
Struct CreateBiTree(BiTree &T, char arr[]){
if(arr[i] == ' '){
i++;
T = NULL;
}else{
T = (BiTree)malloc(sizeof(BiNode));
if(!T){
exit(ERROR);//分配空间失败;
}
T->data = arr[i];
i++;
CreateBiTree(T->lchild, arr);
CreateBiTree(T->rchild, arr);
}
return OK;
}


3:递归遍历二叉树(先序,中序,后序)

这是三中常规的遍历二叉树的方法,用的非常多,不仅便于理解,而且三个函数只是调用顺序不同,所以书上页数先给出了这三种遍历。当然在面试中也长问到。

//先序遍历(递归方法)
int PrintTree(BiTree T){
if(T){
printf("%c ",T->data);
PrintTree(T->lchild);
PrintTree(T->rchild);

}
return OK;
}

//中序遍历(递归方法)
int PrintTreeL(BiTree T){
if(T){
PrintTreeL(T->lchild);
printf("%c ",T->data);
PrintTreeL(T->rchild);
}
return OK;
}

//后序遍历(递归方法)
int PrintTreeR(BiTree T){
if(T){
PrintTreeR(T->lchild);
PrintTreeR(T->rchild);
printf("%c ",T->data);
}
return OK;
}


4:非递归遍历二叉树(先序,中序,后序)

非递归遍历在面试中问的非常多,特别是大公司的面试,几乎是必问,所以这里就非递归的方法每个给出了两种

一、先序遍历:

  先序遍历这里给出了两个方法,两种方法都是借助于栈来实现,具体的栈的操作在最后源程序中都有,看家可以看看,非递归的操作比递归麻烦得多,第一种方法是:首先根节点入栈,然后while循环一致判断栈是否为空,第二个while循环一直讲左节点入栈,知道左子树为空。其中PopN(S); 方法是为了将多余的空元素弹出。第二种方法比较推荐开始根节点不入栈,先判断根节点是否为空,这样栈空间中不会有空元素,效率高一点,并且便于理解

//先序遍历(非递归方法)(方法1)
int PrintTree_Re(BiTree T){
SqStack S;
InitStack(S);//建立栈
Push(S,T); //树的根节点先入栈
BiTree P;
while(StackEmpty(S) == 1){
while(GetTop(S,P) && P){
printf("%c ",P->data);
Push(S,P->lchild);
}
PopN(S);
if(StackEmpty(S) == 1){
Pop(S,P);
Push(S,P->rchild);
}
}
return OK;
}

//先序遍历(非递归方法)(方法2)
int PrintTree_Re_T(BiTree T){
SqStack s;
InitStack(s);
BiTree p;
p = T;
while(p || StackEmpty(s) == 1){
while(p){
printf("%c ",p->data);
Push(s,p);
p = p->lchild;
}
if(StackEmpty(s) == 1){
Pop(s,p);
p = p->rchild;
}
}
return OK;
}


二、中序遍历:

中序遍历这里也给出2种方法。感觉和先序差不多,只是遍历顺序不同,处理的时候方式一样,这里不再多说。

//中序遍历(非递归)(方法1)
int PrintTreeL_Re(BiTree T){
SqStack S;
InitStack(S);//建立栈
Push(S,T); //树的根节点先入栈
BiTree P;
while(StackEmpty(S) == 1){
while(GetTop(S,P) && P){
Push(S,P->lchild);
}
PopN(S);
if(StackEmpty(S) == 1){
Pop(S,P);
printf("%c ",P->data);
Push(S,P->rchild);
}
}
return OK;
}

//中序遍历(非递归)(方法2)
int PrintTreeL_Re_T(BiTree T){
SqStack S;
InitStack(S);//建立栈
BiTree P;
P = T;
while(P || StackEmpty(S) == 1){
if(P){
Push(S,P);
P = P->lchild;
}else{
Pop(S,P);
printf("%c ",P->data);
P = P->rchild;
}
}
return OK;
}


二、后序遍历:

个人感觉后序遍历的非递归是三种遍历方式中最难的。写了很长时间,总感觉找不到一种合适的方法来保证孩子节点完全遍历。下面来说说这种非递归的遍历。其实会了也比较简单,对于结点P,要保证根结点在左孩子和右孩子访问之后才能访问,先将其入栈。如果P不存在左孩子和右孩子,可以直接访问;或者P 存在左孩子或者右孩子,访问过左孩子和右孩子后可以直接访问该结点。其他情况,则将P的右孩子和左孩子依次入栈。这个就会这一种,应该有其他方法,欢迎指出

//后序遍历(非递归方法)
int PrintTreeR_Re(BiTree T){
SqStack s;
InitStack(s);//建立栈
Push(s,T); //树的根节点先入栈
BiTree p,q;
while(StackEmpty(s) == 1){
GetTop(s,p);
if((p->lchild == NULL && p->rchild == NULL) || ( q && ( q == p->lchild || q == p->rchild))){
printf("%c ",p->data);
PopN(s);
q = p;
}else{
if(p->rchild){
Push(s,p->rchild);
}
if(p->lchild){
Push(s,p->lchild);
}
}
}
return OK;
}


5:求二叉树中的节点个数

其实就是遍历一遍二叉树,不再多说,看程序便知

//统计二叉树节点的个数
void getTreeNodeNum(BiTree T,int &num){
if(!T){
num = 0;
}
num++;
if(T->lchild){
getTreeNodeNum(T->lchild,num);
}
if(T->rchild){
getTreeNodeNum(T->rchild,num);
}
}


6:求二叉树的深度

思想就是从左子树开始遍历,递归完整个树结构,如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1,参考代码如下:

//得到树的深度
int getTreeHeight(BiTree T){
int hl,hr,max;
if(T!=NULL){
hl=getTreeHeight(T->lchild);
hr=getTreeHeight(T->rchild);
max=hl>hr?hl:hr;
return (max+1);
}else{
return 0;
}
}


7:分层遍历二叉树

一、树的层次结构分层打印与队列的的先进先出相似,所以我选择用队列来完成,首先根节点入队列,然后左孩子,又孩子以此入队列,打印即可

/*层次遍历二叉树(借助队列实现)*/
void HierarchicalTree_Q(BiTree T){
LinkQueue Q;
InitQueue(Q); //建立队列
EnQueue(Q,T); //树的根节点先入列
BiTree Ta;
while(Q.front != Q.rear){
DeQueue(Q,Ta);
if(Ta->lchild){
EnQueue(Q,Ta->lchild);
}
if(Ta->rchild){
EnQueue(Q,Ta->rchild);
}
printf("%c ",Ta->data);
}
}


二、说到分层如果打印树的真实结构应该也可以,所以我想借助堆栈来实现,有兴趣的可以看一下,思想:其实和层次遍历一样,最难的是怎样控制换行先,我的想法是创建一个标志位*,当前行入栈完毕就插入标志位*遍历一个节点就把左孩子和右孩子入栈,以此一层一层的打印,代码检验过,不知道还有没有错误,欢迎指正

/*层次遍历二叉树(并且分层打印)(借助栈实现)*/
void  HierarchicalTree_DA(BiTree T){
LinkQueue Q;
InitQueue(Q);//建立队列
EnQueue(Q,T);//树的根节点先入列
BiTree Ta1,Tb1,Tc1;

//创建标志位
char data[3] = {'*',' ',' '};
CreateBiTree(Ta1,data);

//创建移动的指针
QueuePtr point;
point = Q.front->next;//移动指针先指向头结点
EnQueue(Q,Ta1);//插入标志位
printf("\n");
while(point){
//判断左右节点
if(point->data->lchild || point->data->rchild){
if(point->data->lchild){
EnQueue(Q,point->data->lchild);
}
if(point->data->rchild){
EnQueue(Q,point->data->rchild);
}
}
point = point->next; //指针下移一位
if(point->data->data == '*'){
if(!point->next){//判断是否到结尾(到结尾直接退出)
break;
}
EnQueue(Q,Ta1);//插入标志节点
point = point->next; //如果遇到标志位,则指针下移
}
}

printf("\n");
//循环输出节点
while(Q.front != Q.rear){
DeQueue(Q,Tc1);
char str = Tc1->data;
if(str == '*'){
printf("\n");
}else{
printf("%c ",str);
}
}
}


8:求二叉树第K层的节点个数

利用递归的次数来(孩子节点不为空的条件)来简介得到k层节点的个数,比较简单,代码如下

//得到第k层的节点个数
int getNodeNum(BiTree T, int k){
if (!T || k < 1){
return 0;
}
if (k == 1){
return 1;
}
return getNodeNum(T->lchild, k-1) + getNodeNum(T->rchild, k-1);
}


9:求二叉树的镜像

所谓的镜像其实就是左子树和右子树互换颠倒,如下面代码。

//写出二叉树的镜像
void getNoClone(BiTree &T){
BiTree Ta;
if(T){
Ta = T->lchild;
T->lchild = T->rchild;
T->rchild = Ta;
getNoClone(T->lchild);
getNoClone(T->rchild);
}
}


10:测试源码

最后附上完整的测试代码,其中包含了,队列的初始化,插入队列,出队列操作,栈的初始化,进栈,出栈,得到栈顶元,树的初始化素等也可以复习一下。中间可定有不对不足的地方,还望大神们多多指正

 /**
* 树的基本操作tree
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ERROR -1
#define OK 1
#define OVERFLOW 2
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 20

//最大队列长度
#define MAXQSIZE 100

typedef int Struct;
typedef char TElement;
typedef int Status;
int i = 0; //记录树初始化过程,数据的偏移
int j = 0; //记录树初始化过程,数据的偏移

//二叉树结构体(二叉链表存储结构)
typedef struct BiNode{
TElement data;
struct BiNode *lchild,*rchild;
}BiTNode, *BiTree;

//栈表示
typedef struct{
BiTree *base;
BiTree *top;
int stacksize;
}SqStack;

//使用队列,实现层次遍历
typedef struct QNode{
BiTree data;
struct QNode *next;
}QNode, *QueuePtr;

typedef struct{
QueuePtr front;
QueuePtr rear;
}LinkQueue;

//初始化队列
Status InitQueue(LinkQueue &Q){
Q.front = Q.rear = (QueuePtr)malloc(sizeof(BiTree));//动态分配空间
if(!Q.front){
exit(ERROR);//分配空间失败
}
Q.front->next = NULL;
return OK;
}

//在队尾插入新元素
Status EnQueue(LinkQueue &Q, BiTree e){
QueuePtr p;
p = (QueuePtr)malloc(sizeof(BiTree));
if(!p){
exit(ERROR);//分配失败
}
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}

//删除队头元素,并用e返回
void DeQueue(LinkQueue &Q,BiTree &e){

if(Q.front != Q.rear){//先判断队列是否为空
QueuePtr p;
e = Q.front->next->data;
if(Q.front->next == Q.rear){//队列只有一个元素
p = Q.rear;
Q.rear = Q.front;
Q.front->next = NULL;
}else{
p = Q.front->next;
Q.front->next = p->next;
p->next = NULL;
}
//free(p->data);
}
}

//创建二叉树 Struct CreateBiTree(BiTree &T, char arr[]){ if(arr[i] == ' '){ i++; T = NULL; }else{ T = (BiTree)malloc(sizeof(BiNode)); if(!T){ exit(ERROR);//分配空间失败; } T->data = arr[i]; i++; CreateBiTree(T->lchild, arr); CreateBiTree(T->rchild, arr); } return OK; }

//先序遍历(递归方法)
int PrintTree(BiTree T){
if(T){
printf("%c ",T->data);
PrintTree(T->lchild);
PrintTree(T->rchild);

}
return OK;
}

//中序遍历(递归方法)
int PrintTreeL(BiTree T){
if(T){
PrintTreeL(T->lchild);
printf("%c ",T->data);
PrintTreeL(T->rchild);
}
return OK;
}

//后序遍历(递归方法)
int PrintTreeR(BiTree T){
if(T){
PrintTreeR(T->lchild);
PrintTreeR(T->rchild);
printf("%c ",T->data);
}
return OK;
}

//初始化栈
Struct InitStack(SqStack &S){
S.base = (BiTree *)malloc(STACK_INIT_SIZE * sizeof(BiTree));
S.top = S.base;
if(!S.base){
exit(ERROR);//分配失败
}
S.stacksize = STACK_INIT_SIZE;
return OK;
}

//push入栈操作
Struct Push(SqStack &S, BiTree T){
if(S.top - S.base >= S.stacksize){
S.base = (BiTree *)realloc(S.base,(S.stacksize + STACKINCREMENT) * sizeof(BiTree));
if(!S.base){
exit(ERROR);
}
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top = T;
S.top++;

return OK;
}

//出栈操作
Struct Pop(SqStack &S,BiTree &P){
if(S.base != S.top){
P = *(--S.top);
}
return OK;
}

//出栈操作
Struct PopN(SqStack &S){
if(S.base != S.top){
--S.top;
}
return OK;
}

//得到栈顶元素操作
Struct GetTop(SqStack S,BiTree &P){
if(S.base != S.top){
P = *(S.top - 1);
}
return OK;
}

//判断是否是空栈
int StackEmpty(SqStack S){
if(S.base != S.top){
return 1;
}else{
return 0;
}
}

//返回栈长度
int GetLength(SqStack S){
return S.top - S.base;
}

//先序遍历(非递归方法)(方法1)
int PrintTree_Re(BiTree T){
SqStack S;
InitStack(S);//建立栈
Push(S,T); //树的根节点先入栈
BiTree P;
while(StackEmpty(S) == 1){
while(GetTop(S,P) && P){
printf("%c ",P->data);
Push(S,P->lchild);
}
PopN(S);
if(StackEmpty(S) == 1){
Pop(S,P);
Push(S,P->rchild);
}
}
return OK;
}

//先序遍历(非递归方法)(方法2)
int PrintTree_Re_T(BiTree T){
SqStack s;
InitStack(s);
BiTree p;
p = T;
while(p || StackEmpty(s) == 1){
while(p){
printf("%c ",p->data);
Push(s,p);
p = p->lchild;
}
if(StackEmpty(s) == 1){
Pop(s,p);
p = p->rchild;
}
}
return OK;
}

//中序遍历(非递归)(方法1)
int PrintTreeL_Re(BiTree T){
SqStack S;
InitStack(S);//建立栈
Push(S,T); //树的根节点先入栈
BiTree P;
while(StackEmpty(S) == 1){
while(GetTop(S,P) && P){
Push(S,P->lchild);
}
PopN(S);
if(StackEmpty(S) == 1){
Pop(S,P);
printf("%c ",P->data);
Push(S,P->rchild);
}
}
return OK;
}

//中序遍历(非递归)(方法2)
int PrintTreeL_Re_T(BiTree T){
SqStack S;
InitStack(S);//建立栈
BiTree P;
P = T;
while(P || StackEmpty(S) == 1){
if(P){
Push(S,P);
P = P->lchild;
}else{
Pop(S,P);
printf("%c ",P->data);
P = P->rchild;
}
}
return OK;
}

//后序遍历(非递归方法)
int PrintTreeR_Re(BiTree T){
SqStack s;
InitStack(s);//建立栈
Push(s,T); //树的根节点先入栈
BiTree p,q;
while(StackEmpty(s) == 1){
GetTop(s,p);
if((p->lchild == NULL && p->rchild == NULL) || ( q && ( q == p->lchild || q == p->rchild))){
printf("%c ",p->data);
PopN(s);
q = p;
}else{
if(p->rchild){
Push(s,p->rchild);
}
if(p->lchild){
Push(s,p->lchild);
}
}
}
return OK;
}

/*层次遍历二叉树*/
void HierarchicalTree(BiTree T){
BiTree Ta = T;
if(Ta){
printf("%c ",Ta->data);
if(Ta->lchild){
//printf("%c ",Ta->lchild->data);
HierarchicalTree(Ta->lchild);
}
if(Ta->rchild){
//printf("%c ",Ta->rchild->data);
HierarchicalTree(Ta->rchild);
}
}
}

/*层次遍历二叉树(借助队列实现)*/
void HierarchicalTree_Q(BiTree T){
LinkQueue Q;
InitQueue(Q); //建立队列
EnQueue(Q,T); //树的根节点先入列
BiTree Ta;
while(Q.front != Q.rear){
DeQueue(Q,Ta);
if(Ta->lchild){
EnQueue(Q,Ta->lchild);
}
if(Ta->rchild){
EnQueue(Q,Ta->rchild);
}
printf("%c ",Ta->data);
}
}

/*层次遍历二叉树(并且分层打印)(借助队列实现)*/
void HierarchicalTree_DA(BiTree T){
LinkQueue Q;
InitQueue(Q);//建立队列
EnQueue(Q,T);//树的根节点先入列
BiTree Ta1,Tb1,Tc1;

//创建标志位
char data[3] = {'*',' ',' '};
CreateBiTree(Ta1,data);

//创建移动的指针
QueuePtr point;
point = Q.front->next;//移动指针先指向头结点
EnQueue(Q,Ta1);//插入标志位
printf("\n");
while(point){
//判断左右节点
if(point->data->lchild || point->data->rchild){
if(point->data->lchild){
EnQueue(Q,point->data->lchild);
}
if(point->data->rchild){
EnQueue(Q,point->data->rchild);
}
}
point = point->next; //指针下移一位
if(point->data->data == '*'){
if(!point->next){//判断是否到结尾(到结尾直接退出)
break;
}
EnQueue(Q,Ta1);//插入标志节点
point = point->next; //如果遇到标志位,则指针下移
}
}

printf("\n");
//循环输出节点
while(Q.front != Q.rear){
DeQueue(Q,Tc1);
char str = Tc1->data;
if(str == '*'){
printf("\n");
}else{
printf("%c ",str);
}
}
}

//统计二叉树节点的个数
void getTreeNodeNum(BiTree T,int &num){
if(!T){
num = 0;
}
num++;
if(T->lchild){
getTreeNodeNum(T->lchild,num);
}
if(T->rchild){
getTreeNodeNum(T->rchild,num);
}
}

//得到树的深度
int getTreeHeight(BiTree T){
int hl,hr,max;
if(T!=NULL){
hl=getTreeHeight(T->lchild);
hr=getTreeHeight(T->rchild);
max=hl>hr?hl:hr;
return (max+1);
}else{
return 0;
}
}

//得到第k层的节点个数
int getNodeNum(BiTree T, int k){
if (!T || k < 1){
return 0;
}
if (k == 1){
return 1;
}
return getNodeNum(T->lchild, k-1) + getNodeNum(T->rchild, k-1);
}

void main(){
i = 0;
//char Data[15] = {'A','B','C',' ',' ','D','E',' ','G',' ',' ','F',' ',' ',' '};
//char Data[15] = {'A','B','C',' ',' ','D','E',' ',' ','G',' ',' ','F',' ',' '};
char Data[16] = {'A','B','C',' ',' ',' ','D','E',' ','G',' ',' ','F',' ',' ',' '};
//char Data[7] = {'A','B','C',' ',' ',' ',' '};
//char Data[7] = {'A',' ','b',' ','c',' ',' '};
BiTree Ta,Tb;
CreateBiTree(Ta,Data);
printf("*****************递归方法遍历二叉树**************\n");
printf("先序遍历:");
PrintTree(Ta);

printf("\n");
printf("中序遍历:");
PrintTreeL(Ta);

printf("\n");
printf("后序遍历:");
PrintTreeR(Ta);
printf("\n");

printf("非递归先序遍历法1:");
PrintTree_Re(Ta);
printf("\n");

printf("非递归先序遍历法2:");
PrintTree_Re_T(Ta);
printf("\n");

printf("非递归中序遍历法1:");
PrintTreeL_Re(Ta);
printf("\n");

printf("非递归中序遍历法2:");
PrintTreeL_Re_T(Ta);
printf("\n");

printf("非递归后序遍历:");
PrintTreeR_Re(Ta);
printf("\n");

printf("层次遍历:");
HierarchicalTree_Q(Ta);
printf("\n");

printf("\n");
printf("层次遍历(分层显示):");
i = 0;
HierarchicalTree_DA(Ta);
printf("\n");

printf("总结点个数:");
int TreeNum = 0;
getTreeNodeNum(Ta,TreeNum);
printf("%d ",TreeNum);
printf("\n");

printf("树的深度:");
int TreeHeight = 0;
TreeHeight = getTreeHeight(Ta);
printf("%d ",TreeHeight);
printf("\n");

printf("得到第3层的节点个数:");
printf("%d",getNodeNum(Ta,3));
printf("\n");

}


运行结果:



转载请注明出处,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: