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=p->rchild; //对右子树全部做同样操作
push(S,p);
p=p->lchild;
}
else{ //没有右子树或右子树访问过了,此时可以输出栈中节点值,这是个根节点(没有左子树也没有右子树或根据后序遍历右子树已经访问过了,此时可以输出根节点了)
pop(S,p);
printf("%d",p->data);
r=p; //记录最近访问的结点
p=NULL; //结点访问完后,重置p指针
}
}
}
}
4.试给出二叉树自下而上,从右到左的层次遍历算法(层次遍历算法)
一般层次遍历时自上而下从左到右,这里遍历顺序恰好相反
利用原有的层次遍历算法,出队的同时将各节点指针入栈,在所有节点入栈后再从栈顶开始依次访问。
具体实现如下:1、根节点入队。2、一个元素出队,遍历这个元素。3、依次把这个元素的右孩子左孩子入队。4、队列不空则跳到2,否则结束。
void InvertLevel(BinTree bt){
Stack S;
Queue Q;
BiTree p;
InitStack(S); //初始化栈,栈中存放二叉树结点的指针
InitQueue(Q); //初始化队列,队列中存放二叉树结点的指针
EnQueue(Q,bt);
while(!IsEmpty(Q)){
DeQueue(Q,p); //出队入栈
Push(S,p); //入栈
if(p->lchild)
EnQueue(Q,p->lchild); //入队左右孩子
if(p->rchild)
EnQueue(Q,p->rchild);
} //队空时,全部进栈了
while(!IsEmpty(S)){
Pop(S,p); //出栈可得反向序列
printf("%d",p->data);
}
}
基于层次遍历
void LevelOrder(BiTree T){
Queue Q;
InitQueue(Q);
BiTree p;
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
visit(p);
if(p->lchild!=NULL) EnQueue(Q,p->lchild);
if(p->rchild!=NULL) EnQueue(Q,p->rchild);
}
}
5.假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度(层次遍历算法)
采用层次遍历的算法,设置变量level记录当前节点所在层数,设置变量last指向当前层最右节点,每次层次遍历出队时与last指针比较,若两者相等,则层数加一,并让last指向下一层的最右节点,直到遍历完成。level的值即为树的高度。
非递归
int Btdepth1(BinTree T){
if(T==NULL) return 0;
int front=-1,rear=-1;
int last=0,level=0;
BinTree Q[MaxSize];
Q[++rear]=T; //入队
BinTree 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++; //妙,rear每次指向层中最后一个节点
last=rear;
}
}
return level;
}
递归
int Btdepth2(BinTree T){
if(T==NULL) return 0; //递归出口
int ldep=Btdepth(T->lchild);
int rdep=Btdepth(T->rchild);
if(ldep>rdep) return ldep+1; //树的高度=子树最大高度+1
else return rdep+1;
}
6.根据前序序列和中序序列构造二叉树,假设结点数据值互不相同。
确定根节点在中序中的位置,将中序序列分为两个子序列,递归处理直到所处理的子序列只剩下一个元素
BTnode *CreateBt(char pre[],char in[],int l1,int r1,int l2,int r2){
BTnode *s=(BTnode *)malloc(sizeof(BTnode));
int i;
if(l1>r1) return NULL;
s->lchild=s->rchild=NULL;
for(i=l2;i<=r2;i++)
if(in[i]==pre[l1]) break;
s->data=in[i]; //确定根节点位置
s->lchild=CreateBt(pre,in,l1+1,l1+i-l2,l2,i-1);
s->rchild=CreateBt(pre,in,l1+i-l2+1,r1,i+1,r2);
return s;
}
7.二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法(层次遍历算法)
根据完全二叉树的定义,具有n个结点的完全二叉树和满二叉树中编号从1-n的结点一一对应。
采用层次遍历算法,将所有结点加入队列(包括空节点)。遇到空节点时,看其后是否右非空节点。若有,则不是完全二叉树。
bool IsComplete(BiTree T){
InitQueue(Q);
if(T==NULL)
return 1;//空树为满二叉树
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
if(p!=NULL){
EnQueue(Q,p->rchild);
EnQueue(Q,p->lchild);
}
else
while(!IsEmpty(Q)){
//遍历的时候空节点也入队了,此时表示出现结点为空结点而队列非空
//若空节点之后又出现非空节点,那么一定不是完全二叉树
DeQueue(Q,p);
if(p) return 0;
}
}
return 1;
}
8.假设二叉树采用二叉链表存储结构存储,设计一个算法,计算给定二叉树的所有双分支结点个数(中序遍历算法)
递归模型 f(b) 如下:(是双分支结点,则此节点+左右子树中的双分支节点,否则不加)
int DsonNode(BiTree b){
if(b==NULL)
return 0;
else if(b->rchild!=NULL&&b->lchild!=NULL)
return DsonNode(b->rchild)+DsonNode(b->lchild)+1; //是双分支就+1
else
return DsonNode(b->rchild)+DsonNode(b->lchild); //不是就+0
}
改造二叉树递归遍历算法
(二叉树的前中后序遍历算法中的visit根节点,可以根据题目的需要做改变)设计一个全局变量cnt用于计数双分支结点个数
int cnt=0;
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild);
if(T->lchild!=NULL&&T->rchild!=NULL)
cnt++;
InOrder(T->rchild);
}
}
9.设树B是一棵采用链式结构存储的二叉树,编写一个把树B中所有节点的左右子树交换的函数(后序遍历算法)
采用递归算实现交换二叉树的左右子树,首先交换b节点的左孩子的左右子树,然后交换b右孩子的左右子树,最后交换b节点的左右孩子,当结点为空时递归结束(后序遍历的思想)(前中后序都可以,就是对遍历到的每个结点的孩子进行交换,与遍历顺序其实是无关的,前序就是:先交换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时,递归的在左子树中查找,若找到则返回该值,否则继续递归的在右子树中查找,并返回其结果。 (二叉树的遍历算法可以引申出大量的算法题,必须熟练掌握(本题基于前序遍历算法:访问根节点,递归访问左右子树(添加改动:对根节点进行递归出口判断第k个值是否存在)))
int i=1; //记录已经访问过的序号
ElemType PreNode(BiTree b,int k){ //前序遍历根据需要改造
if(b==NULL) return '#';
if(i==k) return b->data;
i++;
char ch;
ch=PreNode(b->lchild,k);
if(ch!='#') return ch;
else{
ch=PreNode(b->rchild,k);
return ch;
//因为题目限定了一定能找到,所以不在左子树必定在右子树,无需进行找不到的判断
}
}
11.已知二叉树以二叉链表存储,编写算法:对于树中每个元素值为x的结点,删去以它为根的子树,释放相应的空间(删除子树:后序遍历算法,找父节点:层次遍历算法)
删除以元素值x为根的子树,只要能删除其左右子树,就可以释放值为x的根节点,因此宜采用后序遍历。
删除值为x的结点,意味着应将其父节点的左右孩子指针置空,用层次遍历易于找到某结点的父节点。本题要求删除树种每个元素值为x的结点的子树,因此要遍历完整二叉树。
void DeleteXTree(BiTree bt){
//后序遍历:删除树,先删左右子树,再删根节点
if(bt){
DeleteXTree(bt->lchild);
DeleteXTree(bt->rchild);
free(bt);
}
}
void Search(BiTree bt,Elemtype x){
Queue Q;
InitQueue(Q);
EnQueue(Q,bt);
if(bt==NULL) return;
if(bt->data==x){
DeleteXTree(bt);
return ;
}
while(!IsEmpty(Q)){
DeQueue(Q,p);
if(p->lchild){
if(p->lchild->data==x){
DeleteXTree(p->lchild);
p->lchild=NULL;
}
else EnQueue(Q,p->lchild);
}
if(p->rchild){
if(p->rchild->data==x){
DeleteXTree(p->rchild);
p->rchild=NULL;
}
else EnQueue(Q,p->rchild);
}
}
}
12.在二叉树中查找值为x的结点,试编写算法打印值为x的结点的所有祖先,假设值为x的结点不多于一个
一般情况下树的题目,用递归比较好
判断左右子树里有没有x,从下向上输出所有祖先结点
bool Ancestors(Node *root,int x){
if(root==NULL) return false;
if(root->data==x) return true; //这两个出口为下一个递归判断条件服务
if(Ancestors(root->lchild,x)||Ancestors(root->rchild,x)){
printf("%d",root->data); //任一孩子有x就输出这个根
return true;
}
return false;
}
13.设一棵二叉树的结点结构为(LLINK,INFO,RLINK),ROOT为指向该二叉树根节点的指针,p和q分别为指向该二叉树种任意两个结点的指针,试编写算法ANCESTOR(ROOT,p,q,r),找到p和q的最近公共祖先结点r。(后序非递归)(递归)
后序遍历非递归:(重在思想,后序遍历中存入栈中的结点全都是其祖先)设p在q的左边:算法思想:采用后序非递归算法,栈中存放二叉树结点的指针,当访问到某结点时,栈中所有元素均为该节点的祖先。后序遍历必然先遍历到结点p,栈中元素均为p的祖先,先将栈复制到另一辅助栈中。继续遍历到结点q时,将栈中元素从栈顶开始逐个到辅助栈中匹配,第一个匹配(即相等)的元素就是最近公共祖先。
typedef struct{
BiTree t;
int tag; //0表示左孩子已被访问,1表示右孩子已被访问
}stack;
stack s[],s1[];
BiTree Ancestor(BiTree ROOT,BiTNode *p,BiTNode *q,BiTNode *r){
int top=0,top1; //top是栈顶位置,用于做匹配
bt=ROOT;
while(bt!=NULL||top>0){
while(bt!=NULL&&bt!=p&&bt!=q){
//后序遍历:根节点不能输出,所以沿左分支向下结点依次入栈
//并标记左孩子已经被访问
while(bt!=NULL){
s[++top].t=bt;
s[top].tag=0;
bt=bt->lchild;
}
}
while(top!=0&&s[top].tag==1){
//设p在q的左侧,遇到p时,栈中元素均为p的祖先
if(s[top].t==p){
//找到了p,则把当前栈中元素拷贝到辅助栈
for(int i=1;i<=top;i++){
s1[i]=s[i];
top1=top;
}
}
if(s[top].t==q){
//找到了q,则把栈中元素与s1中元素做匹配,注意是从栈顶开始匹配,从挨近p结点的位置开始匹配
for(i=top;i>0;i--){
for(j=top1;j>0;j--){
if(s1[j].t==s[i].t)
return s[i].t;
}
}
top--;//退栈
}
}
if(top!=0){ //左子树中没找到,则标记已访问左子树,开始向右分支遍历
s[top].tag=1;
bt=s[top].t->rchild;
}
}
return NULL; //无公共祖先
}
递归:递归遍历左右子树,找pq,递归结束后可以知道pq在左子树还是右子树,
- 若pq都不在左子树,那么他们都在右子树,右子树先遍历到的结点就是公共祖先
- 若left不空,说明左子树中有节点,那么看看右子树,若right时空的,说明全在左子树,那么左子树中先遍历到的就是公共祖先
- 否则,left和right都不空,说明pq在root异侧,则root就是公共祖先
- 注意:结点本身也可以是自己的祖先
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
// 如果 p和q中有等于 root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)
if(root==NULL||root==q||root==p)
return root;
// 递归遍历左右子树,只要在左子树中找到了p或q,则先找到谁就返回谁
struct TreeNode *left = lowestCommonAncestor(root->left, p, q);
struct TreeNode *right = lowestCommonAncestor(root->right, p, q);
// 如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,
//右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
if(left==NULL)
return right;
// 否则,如果 left不为空,在左子树中有找到节点(p或q),
//这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,
//则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
if(right==NULL)
return left;
//否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root
return root;
}
14.假设二叉树采用二叉链表存储,设计算法,求非空二叉树的宽度(即具有结点树最多的那一层的结点个数)
采用层次遍历的方法求出所有节点的层次,并将所有结点和对应层次放在一个队列中,然后通过扫描队列求出各层的结点总数,最大的层结点总数即为二叉树的宽度。
typedef struct{
BiTree data[MaxSize]; //注意:保存的是结点而不是结点内容
int level[MaxSize]; //保存data中相同下标结点的层次
int front,rear;
}Qu;
int BTWidth(BiTree b){
BiTree p;
int k,max,i,n;
Qu.front=Qu.rear=-1;//队列空
Qu.data[++Qu.rear]=b; //根节点入队
Qu.level[Qu.rear]=1; //层次为1
while(Qu.front<Qu.rear){ //数组做队列判断条件是f<r InitQueue,Dequeue,Enqueue的判断条件是队列空
p=Qu.data[++Qu.front]; //数组做队列出队时f++ Dequeue
k=Qu.level[Qu.front]; //出队结点:p 出队结点层次=k
if(p->lchild!=NULL){ //左孩子不空左孩子进队
Qu.data[++Qu.rear]=p->lchild; //数组做队列进队时r++ EnQueue
Qu.level[Qu.rear]=k+1;
}
if(p->rchild!=NULL){
Qu.data[++Qu.rear]=p->rchild;
Qu.level[Qu.rear]=k+1;
}
}
max=0;i=0; //max保存同层最多的结点个数
k=1; //k表示从第一层开始查找
while(i<=Qu.rear){ //i扫描队中所有元素
n=0; //n统计第k层的结点个数
while(i<=Qu.rear&&Qu.level[i]==k){
n++;
i++;
}
k=Qu.level[i];
if(n>max) max=n;
}
return max;
}
15.设有一棵满二叉树(所有节点值均不同),已知其先序序列为pre,设计一个算法求其后序序列post。
对于一般二叉树仅根据先序序列或后序序列不能确定另一个遍历序列,但是满二叉树任意一个节点的左右子树含有相等的节点数,同时,先序序列的第一个节点作为后序序列的最后一个节点。
void PreToPost(ElemType pre[],int l1,int h1,ElemType post[],int l2,int h2){
int half;
if(h1>=l1){
post[h2]=pre[l1];
half=(h1-l1)/2;
PreToPost(pre,l1+1,l1+half,post,l2,l2+half-1); //转换左子树
PreToPost(pre,l1+half+1,h1,post,l2+half,h2-1); //转换右子树
}
}
16.设计一个算法将二叉树的叶节点按从左到右的顺序连成一个单链表,表头指针为head。二叉树按二叉链表的方式存储,链接时用叶节点的右指针域来存放单链表指针
通常使用的树的遍历对叶节点的访问都是从左到右,所以都可以,这里采用中序递归遍历。设置前驱指针为pre,初始为空。第一个叶节点由指针head指向,遍历到叶节点时,就将它前驱的rchild指针指向它,最后一个叶节点的rchild为空。
Link head,pre=NULL;
LinkList InOrder(BiTree bt){
if(bt){
InOrder(bt->lchild);
if(bt->lchild==NULL&&bt->rchild==NULL){
if(pre==NULL){
head=bt;
pre=bt;
}
else{
pre->rchild=bt;
pre=bt;
}
InOrder(bt->rchild);
pre->rchild=NULL;
}
}
return head;
}
17.试设计判断两棵二叉树是否相似的算法。所谓二叉树T1T2相似,指的是都空或都只有一个根节点或左子树与左子树相似且右子树与右子树相似
本题采用递归思想,若都空,则相似;若一个空一个不空,则必然不相似;否则递归的比较他们的左右子树是否相似
两空,一空,都不空的三种情况
int Similar(BiTree T1,BiTree T2){
int leftS,rightS;
if(T1==NULL&&T2==NULL) return 1;
esle if(T1==NULL||T2==NULL) return 0;
else{
leftS=similar(T1->lchild,T2->lchild);
rightS=similar(T1->rchild,T2->rchild);
return leftS&&rightS; //仅当都为1的时候返回1
}
}
天勤(1)设计算法计算给定二叉树的所有节点
实质遍历二叉树
int n=0; //先序遍历
void count(BTnode *p){
if(p){
++n;
count(p->lchild);
count(p->rchild);
}
}
//后序遍历
int count(BTnode *p){
int n1,n2;
if(p==NULL){
return 0;
}
else {
n1=count(p->lchild);
n2=count(p->rchild);
return 1+n1+n2;
}
}
(2)计算给定二叉树的所有叶子结点
int n=0; //采用前序遍历
void count(BTnode *p){
if(p){
if(p->lchild==NULL&&p->rchild==NULL) ++n;
count(p->lchild);
count(p->rchild);
}
}
//对应后序遍历
int count(BTnode *p){
int n1,n2;
if(p==NULL){
return 0;
}else if(p->lchild==NULL&&p->rchild==NULL) return 1;
else {
n1=count(p->lchild);
n2=count(p->rchild);
return n1+n2;
}
}
(5)增加一个指向双亲结点的parent指针,设计算法给这个指针赋值,并输出所有节点到根节点的路径
修改二叉链表结点数据结构,将所有结点的parent指针指向双亲结点,打印所有节点到根节点的路径。
typedef struct BTnode{
char data;
struct BTnode *rchild;
struct BTnode *lchild;
struct BTnode *parent;
}BTnode;
//给parent指针赋值
void triBTree(BTnode *p,BTnode *q){//q始终指向当前访问结点p的双亲结点
if(p){
p->parent=q;
q=p;
triBTree(p->lchild,q);
triBTree(p->rchild,q);
}
}
//任给一个节点求其到根节点的路径
void printPath(BTnode *p){
while(p){
printf("%d",p->data);
p=p->parent;
}
}
(6)假设满二叉树b的先序序列存在长度为n的数组中,设计算法将其转换为后序遍历序列
只需将根节点移动到整个序列的末尾,然后继续递归处理序列的前一半和后一半
void change(char pre[],int l1,int r1,char post[],int l2,int r2){
if(l1<=r1){
int half=(l1-r1)/2;
post[r2]=pre[l1];
change(pre,l1+1,half+l1,post,l2,half+l2-1);
change(pre,half+l1+1,r1,post,half+l2,r2-1);
}
}
(7)二叉链表存储二叉树,设计算法求二叉树b中值为x的结点的层号
int L=1;
void high_x(BTnode *b,int x){
if(b){
if(p->data==x) printf("%d",L);
++L;
high_x(p->lchild,x);
high_x(p->rchild,x);
--L;
}
}
(10)二叉树的双序遍历:对于二叉树的每个节点,先访问这个节点,再按照双序遍历它的左子树,然后再一次访问这个节点,再双序遍历右子树。
void Double_order(BTnode *t){
if(t){
Visit(t); //已经定义的访问t的函数
Double_order(t->lchild);
Visit(t) ;
Double_order(t->rchild);
}
}
(11)设中序线索二叉树类型为TBTnode *InThTree
(1)设计算法在一棵中序线索二叉树中寻找节点t的子树上中序下的最后一个节点
沿着t的右子树链一直走下去,直到遇到其右指针为右线索的节点为止
TBTnode *inLast(TBTnode *t){
TBTnode *p=t;
while(p&&!p->rtag) p=p->rchild;
return p;
}
(2)找t的中序前驱
若t有左线索,则其左线索所指结点即为中序前驱;若无左线索,则其左子树中中序最后一个节点为它的中序前驱
TBTnode *inPrior(TBTnode *t){
TBTnode *p=t->lchild;
if(p&&!t->ltag) p=inLast(p);
return p;
}
(3)找t的前序后继
分三种情况:1、节点有左孩子 2、节点只有右孩子 3、节点没有孩子,此时其前序后继是此节点的右线索走到无右线索节点这个节点就是前序后继
TBTnode *preNext(TBTnode *t){
TBTnode *p;
if(!t->ltag) p=t->lchild;
else if(!t->rtag) p=t->rchild;
else{
p=t;
while(p&&p->rtag) p=p->rchild;
if(p) p=p->rchild;
}
return p;
}