一、一棵高度为h的满m叉树有如下性质:根结点所在层次为第1层,第h层上的结点都是叶结点,其余各层上的每个结点都有m棵非空子树,若按层次自顶向下,同一层自左向右,顺序从1开始对全部结点进行编号。
(1)各层的结点个数为多少
答:第一层有m^=1个,第二层有m^1个结点,第三层有m^2个结点.....一般的,第i层有m^i个
(2)编号为i的结点的双亲结点(若存在)的编号为多少
答:在m叉树的情形下,结点i的第一个子女编号为j=(i-1)m+2,反过来结点i的双亲的编号为+1,根结点没有双亲,所以 要求i>1
(3)编号为i的结点的第k个孩子结点(若存在)的编号是多少?
答:因为结点i的第1个子女编号为(i-1)m+2,若设该结点结点子女的序号为k=1,2...m,则第k个子女结点的的编号为(i-1)m+k+1(1<=k<=m)
(4)编号为i的结点有右兄弟的条件是什么?其右兄弟结点的编号是多少?
二、已知一棵二叉树按顺序存储结构进行存储,设计一个算法,求编号分别为i和j的两个结点的最近的公共祖先结点的值
答:二叉树任意两个结点必然存在最近的公共祖先结点,最坏的情况下是根结点(两个结点分别在根结点的左右分支中),而且最近的公共祖先结点到根结点的全部祖先结点都是公共的。由二叉树顺序存储可知,任一结点i的双亲结点的编号为i/2。求解i和j最近公共祖先ji结点的步骤为:
(1)若i>j,则结点i所在层次大于等于结点j所在层次,结点i的双亲结点为结点i/2,若i/2=j,则结点i/2是源节点i和结点j的最近公共 祖先结点,若i/2≠j,则令i=i/2,即以该结点i的双亲结点为起点,采用递归的方法继续查找
(2)若j>i,则结点j所在层次大于等于i所在层次。结点j的双亲结点为j/2,若j/2=i,则结点j/2是原节点i和结点j的最近公共祖先结点,若j/2≠i,则令j=j/2
重复上述过程,直到找到他们最近的公共祖先结点
ElemType Comm_Ancestor(SqTree T,int i,int j){
if(T[i]!='#' && T[j]!='#'){//结点存在
while(i!=j){ //两个编号不同时循环
if(i>j)
i=i/2; //向上找i的祖先
else
j=j/2; //向下找i的祖先
}
return T[i];
}
}
三、综合
1.若某个非空二叉树的先序序列和后序序列正好相反,则该二叉树的形态是什么?
答:先序为根左右,后序为左右根。前序和后序相反。相反则,左根 和根左 ,根右和右根正好相反。
所以左子树或右子树都为空,满足先序序列和后序序列正好相反。每层只有一个结点。
2.若某非空二叉树的先序序列和后序序列正好相同,则该二叉树的形态是什么?
答:先序:根左右,后序:左右根。相同则有,该树的左右子树都为空,只有一个根结点
3.编写后序遍历二叉树的非递归算法
算法思想:后序非递归遍历二叉树的顺序,先访问左子树,再访问右子树,最后访问根结点。当用堆栈来存储结点时,必须分清返回根结点时是从左子树返回的还是右子树返回。所以,使用辅助指针r,其指向最近访问过的结点。也在结点中增加一个标志域,记录是否已被访问。
void PostOrder(BiTree T){
InitStack(S);
p=T;
r=NULL;
while(p||!IsEmpty(S)){
if(p){ //走到最左边
push(S,p);
p=p->lchild;
} //向右
else{
GetTop(S,p); //取栈顶结点
if(p->rchild && p->rchild!=r){ //若右子树存在,且未被访问过
p==->rchild; //转向右
push(S,p); //压入栈
p=p->lchild; //再走到最后
}
else{ //否则,弹出结点并访问
pop(S,p); //将结点弹出
visit(p->data); //访问该结点
r=p; //记录最近访问过的结点
p=NULL; //访问完后,重置p指针
}
}//else
}//while
}
访问一个结点*p时,栈中结点恰好是*p结点的所有祖先。从栈底到顶结点再加上*p结点,刚好构成从根结点到*p结点的一条路径。。
4.试给出二叉树的自下而上,从右到左的层次遍历算法
算法思想:利用常规的层次遍历算法,出队的同时将各结点指针入栈,在所有结点入栈再从栈顶开始依次访问即为所求算法
- 把根结点入队
- 把一个元素出队,遍历这个元素
- 依次把这个元素的右孩子、左孩子入队
- 若队列不空,则跳到第2步,否则结束
void InvertLevel(BiTree bt){
Stack s;
Queue Q;
if(bt!=NULL){
InitStack(s); //栈初始化,栈中存放二叉树结点的指针
InitQueue(Q); //队列初始化,队列中存放二叉树结点的指针
EnQueue(Q,bt);
while(IsEmpty(Q)==false){ //从上而下层次遍历
DeQueue(Q,p);
Push(s,p); //出队,入栈
if(p->child)
EnQueue(Q,p->lchild); //若左子树不空,则入队列
if(p->child)
EnQueue(Q,p->rchild); //若右子树不空,则入队列
}
while(IdEmpty(s)==false){
Pop(s,p);
visit(p->data);
} //自下而上,从右到左的层次遍历
}//if结束
}
5.假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度
采用层次遍历的算法,设置变量level记录当前结点所在层数,设置变量last指向当前层的最右结点,每次层次遍历出队时与last指针比较,若两者相等,则层数加1,并让last指向下一层最右结点,直到遍历完成,level的值即为二叉树的高度。
int Btdepth(BiTree T){
if(!T)
return 0; //树空高度为0
int front=-1,rear=-1;
int last=0,level=0; //last指向下一层第一个结点的位置
BiTree Q[MaxSize]; //设置队列Q,元素是二叉树结点指针且容量足够
Q[++rear]=T; //将根结点入队
BiTree p;
while(front<rear){ //队不空,则循环
p=Q[++front]; //队列元素出队,即正在访问的结点
if(p->lchild)
Q[++rear]=p->lchild; //左孩子入队
if(p->rchild)
Q[++rear]=p->rchild; //右孩子入队
if(front==last){ //处理该层的最右结点
level++; //层数增1
last=rear; //last指向下层
}
}
return level;
}
6.设一棵二叉树中各结点的值互不相同,其先序遍历和中序遍历分别存于两个一维数组A[1,...n]和B[1,...n]中,试编写算法建立该二叉树的二叉链表
由先序序列和中序序列可以唯一确定一棵二叉树。算法步骤:
(1)根据先序序列确定树的根结点
(2)根据根结点在中序序列中划分出二叉树的左右子树包含哪些结点,然后根据左、右子树结点在先序序列中的次序确定子树的根结点,即返回步骤1
如此重复,直到每棵子树仅有一个结点(该子树的根结点)为止,如下图所示
BiTree PreInCreat(ElemType A[],ElemType B[],int l1,int h1,int l2,int h2){
//l1,h1为先序的第一个和最后一个结点下标,l2,h2为中序的第一个和最后一个结点下标
//初始调用时,l1=l2=1,h1=h2=n
root=(BiTNode*)malloc(sizeof(BiTNode)); //建根结点
root->data=A[l1]; //根结点
for(i=12;B[i]!=root->data;i++); //根结点在中序序列中的划分
llen=i-12; //左子树长度
rlen=h2-i; //右子树长度
if(llen) //递归建立左子树
root->lchild=PreInCreat(A,B,l1+1,l1+llen,l2,l2+llen-1);
else //左子树为空
root->lchild=NULL;
if(rlen) //递归建立右子树
root->rchild=PreInCreat(A,B,h1-rlen+1,h1,h2-rlen+1,h2);
else //右子树为空
root->rchild=NULL;
return root; //返回根结点指针
}
7.二叉树按二叉链表形式存储,写一个判别给定二叉树是否完全二叉树的算法
解:根据完全二叉树定义,具有n个结点的完全二叉树与满二叉树中编号1~n的结点一一对应。
采用层次遍历算法,将所有结点加入队列(包括空结点)。遇到空结点时,查看其后是否右非空结点。若有,则二叉树不是完全二叉树。
bool IsComplete(BiTree T){
InitQueue(Q);
if(!T)
return 1; //空树为满二叉树
EnQueue(Q,T);
while(!IsEpty(Q)){
DeQueue(Q,p);
if(p){ //结点非空,将其左、右子树入队
DeQueue(Q,p->lchild);
EnQueue(Q,p->rchild);
}
else //结点为空,检查其后是否有非空结点
while(!IsEmpty(Q)){
DeQueue(Q,p);
if(p) //结点非空,则二叉树为非完全二叉树
return 0;
}
}
return 1;
}
8.假设二叉树采用二叉链表存储结构存储,试设计一个算法,计算一棵给定二叉树的所有双分支结点个数。
解:计算一棵二叉树b中所有双分支结点个数的递归模型f(b)
f(b)=0 //若b=NULL
f(b)=f(b->lchild)+f(b->rchild) //若*b为双分支结点
f(b)=f(b->lchild)+f(b->rchild) //其他情况(*b为单分支结点或叶子结点)
int DsonNodes(BiTree b){
if(b==NULL) return 0;
else if(b->lchild !=NULL && b->rchild!=NULL) //双分支结点
return DsonNodes(b->lchild)+DsonNodes(b->rchild)+1;
else
return DsonNodes(b->lchild)+DsonNodes(b->rchild);
}
9.设树B是一棵采用链式结构存储的二叉树,编写一个把树B中所有结点的左、右子树进行交换的函数
解:采用递归算法实现交换二叉树的左、右子树,首先交换b结点的左孩子的左、右子树,,然后交换b结点 的右孩子的左、右子树,最后交换b结点的左、右孩子,当结点为空时递归结束(后序遍历的思想)
void swap(BiTree b){
if(b){
swap(b->lchild); //递归交换左子树
swap(b->rchild); //递归交换右子树
temp=b->lchild; //交换左、右孩子结点
b->lchild=b->rchild;
b->rchild=temp;
}
}
10.假设二叉树采用二叉链表存储结构存储,设计一个算法,求先序遍历中第k(1<=k<=二叉树中结点个数)个结点的值
解:设置一个全局变量i记录已访问过的结点的序号,其初值是根结点在先序序列中的序号,即1。当二叉树b为空时返回特殊字符‘#’,
当i==k时,表示找到了满足条件的结点,返回b->data;当i≠k时,递归地在左子树中查找,若找到则返回该值,
否则继续递归地在右子树中查找,并返回其结果。
int i=1; //遍历序号的全局变量
ElemType PreNode(BiTree b,int k){
if(b==NULL) //空结点,则返回特殊字符
return '#';
if(i==k) //相等,则当前结点即为第k个结点
return b->data;
i++; //下一个结点
ch=PreNode(b->lchild,k); //左子树递归寻找
if(ch!='#') //在左子树中,则返回该值
return ch;
ch=PreNode(b->rchild,k); //在右子树中递归寻找
return ch;
}