数据结构(四)——树和二叉树练习题

  1. 已知一棵二叉树按照顺序存储结构进行存储,设计一个算法,求编号分别为i和j的两个结点的最近公共祖先结点。
    思路:
    必须明确二叉树中任意两个结点之间必然存在最近的公共祖先结点,最坏的情况下是根结点(只要存在两个结点不是根结点就在根结点的两棵子树中),而且从最近的公共结点到根结点的全部祖先结点都是公共的。而且这里提示二叉树按照顺序存储结构进行存储,所以考虑到在顺序存储结构中父子结点之间的关系——任一结点i的父节点编号为i/2。
    步骤:
    (1)若I (2)若j
//从下标0开始,返回的是存储公共祖先结点的数组下标
int find_sameFather(BSTNode[] node,BSTNode x,BSTNode y){
   int i = x.data;
   int j = y.data;
   if(i<=2||j<=2)
     return 0;
   while(i!=j&&i>2&&j>2){
     if(i<j){
        j = j/2;
       // if(j==i) return j;
       }
     else {
         i =i/2;
        // if(j==i) return i;
      } 
}
   if(i==j)
    return i;
   else
    return 0;
}

  1. 给出二叉树的自下而上,从右到左的层次遍历算法
    思路:可以先使用正常的层次遍历使用栈然后再从栈中弹出元素
void InsertLevel(BiTree bt)
{
   Stack s;Queue Q;
   if(bt!=nullptr){
    InitStack(S);
    InitQueue(Q);
    EnQueue(Q,bt);
    while(!IsEmpty(Q)){
      DeQueue(Q,p);
      Push(S,p);
      if(p->lchild!=nullptr)
        Enqueue(Q,p->lchild);
      if(p->rchild!=nullptr)
        Enqueue(Q,p->rchild);
    }
   while(!IsEmpty(S))
   {
    Pop(S,p);
    visit(p->data);
   }
}
//但是实际上也不用开两个,就直接使用一个数组就行,物理存储都是一样的,只不过逻辑结构不同而已
#define MaxSize 100000
void  InsertLevel(BiTree bt){
   BiTree temp[MaxSize];
   int top=0;
   int begin=0;
   p = T;
   temp[top++]=bt;
   while(begin!=top){//等到开始的结点控制位置和后面的重合就可以了
      if(temp[begin]->lchild!=nullptr)
          temp[top++]=temp[begin]->lchild;
      if(temp[begin]->rchild!=nullptr)
          temp[top++]=temp[begin]->rchild;
      begin++;
}
   for(int i=begin-1;i>=0;i--)
      visit(temp[i]->data);
}
  1. 假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度
    思路:
    (1)层次遍历,设置变量last记录当前结点所在层数,设置变量last指向当前层最右结点,每次层次遍历出队时和last指针比较,如果两者相等,则层数+1,并让last指向下一层最右结点,直到遍历完成。
    (2)深度优先遍历。使用两个记录层数的变量,level1 和level2 ,每次往下遍历的时候level1和level2都+1,遍历到叶子节点的时候,应该回溯,此时level1不变,level2–,然后level2到继续往下遍历的时候再level2++,等到这次到达叶子结点level2和level1比较,如果如果level1>level2,level1不变,继续遍历;如果level1
int Btdepth(BiTree T)
{
   if(!T)  return 0;
   int front = -1,rear = -1;
   int last = 0,level = 0;
   BiTree Q[MaxSize];
   Q[++rear]=T;//rear队尾指针,队尾指针实际上就是每层更新后的最后一个元素的位置啊
   BiTree p;
   while(front!=rear){
     p = Q[++front]; //当前处理节点,当前访问到的结点
     if(p->lchild!=nullptr)
        Q[++rear]=p->lchild;
     if(p->rchild!=nullptr)
        Q[++rear]=p->rchild;
     if(front == last)
     //当前访问结点和当前层最右结点相等时才更新rear
     //否则如果在if(front==last)外更新的话,front直到最后才能赶上rear(last)
     //而且当前层遍历结束了也正好代表当前层的所子结点都已经进入队列,也就是当前的rear就是下一层的最右结点
     //而且判断得在将当前访问结点的子节点入队列之后才能访问,否则每层都没有完全进入
     {  level++;
        last = rear;
     }    
   }   
return level
}
  1. 设一棵二叉树中各结点的值互不相同,其先序遍历序列和中序遍历序列分别存在于两个一维数组A[1…n]和B[1…n]中,试编写算法建立该二叉树的二叉链表
    思路:
    (1)根据先序序列先确定根结点
    (2)根据结点在中序序列中划分出二叉树的左右子树都包含哪些结点
BiTree PreInCreat(ElemType A[],ElemType B[],int l1,int h1,int l2,int h2){
//A先序,B中序,l1,h2先序序列的开始和结束,l2,h2中序序列的开始和结束
   root = (BiTNode*)malloc(sizeof(BiTNode));
   root->data = A[l1];
   /*
   for(int i=l2;i<=h2;i++)
     if(B[i] == A[l1]);
        temp = i;
   */
   for(int i=l2;B[i]!=root->data;i++);//上面的还得把整个数组B遍历完,这个到对应的位置就结束了
   int llen = i-l2;//可以通过中序遍历知道左右子树的结点数
   int rlen = h2-i;//即使是在先序遍历中一棵子树上的结点也是连续的排列在终先序序列中的
   if(llen)
      root->lchild=PreInCreat(A,B,l1+1,l1+llen,l2,l2+llen-1);
   else//当分割成的长度为0的时候要赋值为空
      root->lchild = nullptr;
   if(rlen)
      root->rchild=PreInCreat(A,B,h1-rlen+1,h1,h2-rlen +1,h2);
   else
      root->rchild=nullptr;
   
   return root;
}
  1. 二叉树采用二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法
    思路:完全二叉树的定义——具有n个结点的完全二叉树与满二叉树中编号从1~n的结点一一对应

⚠️判断为完全二叉树的方法:采用层次遍历的算法,将所有结点加入队列(包括空结点),当遇到空结点时查看其后时候有非空结点,若有,则二叉树不是完全二叉树。(空结点后面如果有值存在一定不是完全二叉树)

[如果是完全二叉树变成顺序存储表示一定是一个连续的数组,那么就把按照二叉链表存储的二叉树转换为使用顺序存储表示然后看是否有空的。]

bool IsComplete(BiTree bt){
    if(bt == nullptr) return true;
    InitQueue(Q);
    EnQueue(Q,bt);
    BiTree p;
    while(!IsEmpty(Q))
    {
       DeQueue(Q,p);
       if(p){
          EnQueue(Q,p->lchild);
          EmQueue(Q,p->rchild);
    }else{
         while(!IsEmpty(Q)){
         //当找到一个空结点之后直接将元素出队判断后面是否是非空元素,如果是,一定不是完全二叉树
         //队列是先进先出的,所以才要队列非空不断出队判断出队结点是否正常
           DeQueue(Q,p);
           if(p)
             return false;
          }
        }
    return true;
}
//太麻烦了,咩有必要
bool IsComplete(BiTree bt)
{
  if(bt==nullptr) return true;
  BiTree Q[MaxSize];
  int front=-1,rear=-1;
  BiTree p = bt;
  Q[++rear]=p;
 while(front<rear){
   p = Q[++front];
   if(p->lchild!=nullptr)
      Q[++rear]=p->lchild;
   else
      Q[++rear]=nullptr;
   if(p->rchild!=nullptr)
      Q[++rear]=p->rchild;
   else
      Q[++rear]=nullptr;
  }
  for(int i = 0;i<=rear;i++)
     if(Q[i]==nullptr)
     {
      for(int j=i+1;j<=rear;j++)
        if(Q[j]!=nullptr)
          return false;
}
  return true;
}


  1. 假设二叉树采用二叉链表形式存储,设计一个算法,计算一颗给定的二叉树的所有双分支结点的个数
    (1)递归模型
    f(b)=0;//b=nullptr
    f(b)=f(b->lchild)+f(b->rchild)+1;//若b为双分支结点
    f(b)=f(b->lchild)+f(b->rchild);//其他情况
    双分支结点处+1,其他位置继续向下计算左右子树的双分支结点数
    (2)设置一个全局变量,直接遍历每个结点然后判断每个结点是否有两个子节点
//方法一
int calculate(BiTree bt)
{
    if(bt == nullptr)
      return 0;
   if(bt->rchild!=nullptr&&bt->lchild!=nullptr)
   // 凡是有左右两个字节点的都会在这里+1
      answer = calculate(bt->lchild)+calculate(bt->rchild)+1;//别忘了根结点如果有左右结点的话应该+1
   else
      answer =calculate(bt->lchild)+calculate(bt->rchild);     
}
//方法二
int num =0;
int calculate(BiTree bt)
{
  if(bt==nullptr)
     return num;
  if(bt->lchild!=nullptr&&bt->rchild!=nullptr)
     num++;
  calculate(bt->lchild);
  calculate(bt->rchild);
}
  1. 设树B是一棵采用二叉链表存储结构的二叉树,编写一个把树B中的所有结点的左、右子树进行交换的函数
BiTree swap(BiTree bt){
   if(bt == nullptr) return bt;
   /*
   实际上这里的条件判断都没有用,如果根结点是nullptr的话直接从递归条件退出去了
   交给退出条件就行,即使子节点有一个是空的也得换啊,所以也不用非得是两个都非空才能换
   二叉树一个结点位置也是有影响的。
   if(bt -> rchild!=nullptr&&bt->lchild!=nullptr)
      BiTree temp;
      temp = bt->rchild;
      bt->rchild = bt->lchild;
      bt->lchild = temp;
   }
   else if(bt->lchild!=nullptr)
      transform(bt->lchild);
   else if(bt->lchild!=nullptr)
      transform(bt->rchild);
   */
   swap(bt->lchild);
   swap(bt->rchild);
   BiTree temp;
   temp = bt->lchild;
   bt->lchild = bt->rchild;
   bt->rchild = temp;
}
  1. 假设二叉树采用二叉链表存储结构,设计一个算法,求先序遍历序列中第k(1<=k<=二叉树中结点个数)个结点的值。
int i=1;
ElemType PreNode(BiTree bt,int k)
{
  if(b==nullptr)
     return '#';
  if(i==k)
     return b->data;
  i++;
  ch = PreNode(b->lchild,k);
  if(ch!='#')
     return ch;
  ch = PreNode(b->rchild,k);
     return ch;
}

//没有考虑bt为空的时候,但是这里说k大于1,所以应该不存在为空的情况
int find_k(Bitree bt,int& k)
{
   if(k==1)
     return bt->data;
   else{
     k--;
     find_k(bt->lchild,k);
     find_k(bt->rchild,k);  
   }
}
  1. 已知二叉树以二叉链表存储,编写算法完成:对于树中每一个元素值为x的结点,删去以它为根的子树,并释放相应的空间
    思路:
    删除以元素值x为根的子树,只要能删除其左、右子树,就可以释放值为x的根结点,宜采用后序遍历
    删除值为x的结点,意味应将其父结点的左右子女指针置空,用层次遍历易于找到某结点的父节点。
void delete_tree(BiTree bt){
   if(bt == nullptr)
    return;
   delete_tree(bt->lchild);
   delete_tree(bt->rchild);
   BiTree p = bt;
   delete p; 
}
/*void condelete(BiTree bt,int x){
    if(bt == nullptr)  return;
    condelete(bt->lchild,x);
    if(bt->data == x)
       delete_tree(bt);
    condelete(bt->rchild,x);
}*/
//这里使用层次遍历更方便,因为一层出队的时候该层的所有子节点都进入队列,所以层次遍历便于找到某结点的父节点
//而且层次遍历从上层往下进行,不必进行回溯
void Search(BiTree bt,ElemType x){
  BiTree Q[];
  //当前二叉树不为空
  if(bt){
  //如果根结点即为x,则直接删除整棵树
   if(bt->data == x)
    {  delete_tree(bt);
      return;}
   //否则进行层次遍历的主体 
   InitQueue(Q);
   EnQueue(Q,bt);
   while(!IsEmpty(Q)){
      DeQueue(Q,p);
      //如果左子树不为空,则判断左子树的根结点是否值为x
      if(p->lchild){
         if(p->lchild->data==x){
          delete_tree(p->lchild);
          p->lchild = nullptr;//别忘了删除掉以该结点为根的子树后,将结点置空                                                                                                  
         }
      else{
        EnQueue(Q,p->lchild);//如果不是,入队继续处理
        }
      //如果右子树不为空,则判断右子树的根结点是否值是否为x
      if(p->rchild){
         if(p-rchild->data==x){
         delete_tree(p->rchild);
         p->rchild = nullptr;}
      else{
      EnQueue(Q,p->rchild);
      }        
   }
 }
}
  1. 设一棵二叉树的结点结构为(LLINK, INFO,RLINK),ROOT为指向该二叉树根结点的指针,p和q的最近公共祖先结点r
    思路:采用非递归后序遍历算法,栈中存放二叉树结点的指针,当访问到某节点时,栈中所有元素均为该节点祖先。假设p在q的左侧,后序遍历先遍历到p,栈中元素均为p的祖先。先将栈复制到另一个辅助栈中,继续遍历到结点q。然后从当前栈的栈顶开始和辅助栈中的元素进行比对,第一个匹配到的元素就是二者的公共祖先结点
typedef struct{
   Bitree t;
   int tag;
   }stack;
 stack s[Maxsize],s1[Maxsize];
 Bitree Ancestor(Bitree root,BitNode* p,BitNode* q){
    int top = 0;
    Bitree bt = root;
    while(bt!=nullptr||top>0)//当不为空且当前栈不为空
    {
      while(bt!=nullptr&&bt!=p&&bt!=q){
         while(bt!=nullptr){//沿左分枝向下遍历
          s[++top].t = bt;
          s[top].tag = 0;
          bt = bt->lchild;
          }
         while(top!=0&&s[top].tag==1){
            if(s[top].t == p)//堆栈复制
            { 
               for(i =1;i<top;i++)
                  s1[i]=s[i];
               top1 = top;
             }
           if(s[top].t == q)//找公共祖先
              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 nullptr;
}

⚠️之前想的是两个循环的嵌套是有顺序的,但是实际上忽略了一个问题就是,q的堆栈不是从p的堆栈继续(要是这样的话就不用求了,求到先遍历到的结点的父节点就是最近公共祖先了,但是这里先遍历到的p的堆栈还有可能退栈才能到q所以这里两层循环的顺序无所谓。

  1. 假设二叉树采用二叉链表存储结构,设计一个算法,求非空二叉树b的宽度(即具有结点数最多的那一层的结点个数)
//self
int calculate_width(Bitree T){
if(T == nullptr) return 0;
if(T->rchild == nullptr && T->lchild == nullptr) return 0;
Bitree Q[Maxsize];
int front = -1,rear = -1;
int length = 0;
int last = rear;
Q[++rear] = T;
while(front!=rear){
 Bitree q = Q[++front];
 if(q->lchild!=nullptr) Q[++rear] = q->lchild;
 if(q->rchild!=nullptr) Q[++rear] = q->rchild;
 if(front == last){
    int now  = rear - last;
    if(length < now) length = now;  
   }
 }
return length;
}

//reference-another
//使用非环形队列,因为要把所有树的结点都保存在队列中,如果是环形队列可能会出现结点被覆盖的问题。
typedef struct{
BiTree data[Maxsize];//保存队列中的结点指针
int level[Maxsize];//保存data中相同下标结点的层次
int front,rear;
}Qu;//整个设置成一个数据结构为了进行遍历操作
int BiWidth(Bitree b){
   Bitree p;
   int k,max,i,n;
   Qu.front = Qu.rear = -1;
   //Qu.rear++;
   Qu.data[++Qu.rear]=b;
   Qu.level[Qu.rear]=1;//从树根开始为第一层,把每层的level进行设置
   while(Qu.front <Qu.rear){//当队列不为空的时候循环操作
       Qu.front++;
       p = Qu.data[Qu.front];//取出当前结点,记录当前结点的level值,在下面儿子结点自然就是对当前值+1操作
       k = Qu.level[Qu.front];
       if( p->lchild!=nullptr){
          //Qu.rear++;
          Qu.data[++Qu.rear] = p->lchild;
          Qu.level[Qu.rear] = k+1;
          }
       if( p->rchild!=nullptr){
          //Qu.rear++;
          Qu.data[++Qu.rear] = p->rchild;
          Qu.level[Qu.rear] = k+1;
         }
   }
   //所有的结点都不出队,通过一次层次遍历将每个结点的level记录下来
   
   //就是一个数组找重复元素最多的个数
   //然后遍历level数组,从第一层开始统计level相同的个数
   max = 0; i = 0; k = 1;
   //max始终记录最大值,i是数组遍历的下标控制,k代表当前统计的层数
   while(i<=Qu.rear){
    n = 0;//n记录每层的结点个数
    while(i<Qu.rear&&Qu.level[i]==k){
       n++;
       i++;
    }
    k = Qu.level[i];//i加到当前不等k,则现在下标i对应的即是下一层的个数
    if(n>max) max = n;
    }
   return max;
      

}
  1. 设有一棵满二叉树(所有结点值均不同),已知其先序序列为pre,设计一个算法求其后序序列post
    思路:
    a/ 因为是满二叉树,所以二叉树的结点总数一定是奇数个,任意结点的左右子树均含有相同的结点数。在先序遍历中的第一个结点一定是后序遍历的最后一个结点,那么分解到每一个小的子树中进行遍历也是满足这个规则的,所以直接用递归:
    先序序列pre[l1...h1]转换为后序序列post[l2...h2]
f(pre,l1,h1,post,l2,h2)=nullptr//l1>h1时,不做任何事
f(pre,l1,h1,post,l2,h2)=post[h2]=pre[l1];
//取中间位置,将左右子树互换,这个时候更新pre和post的头结点的时候要注意pre的头节点要路过,post的尾结点要路过
int half = (h1-l2)/2;
f(pre,l1+1,l1+half,post,l2,l2+half-1);
f(pre,l1+half+1,h1,post,l2+half,h2-1);

void PreToPost(Elemtype pre[],int l1,int h1,Elemtype post[],int l2,int h2){
  if(h1<h1) return;
  post[h2] = pre[l1];//key
  int half = (h1-l2)/2;
  PreToPost(pre,l1+1,l1+half,post,l2,l2+half-1);
  PreToPost(pre,h1-half+1,h1,post,l2+half,h2-1);
}
  1. 设计一个算法将二叉树的叶结点按从左到右的顺序连成一个单链表,表头指针为head。二叉树按二叉链表方式存储,连接时用叶结点的右指针域来存放单链表指针
    思路:使用层次遍历到最后一层结点记录第一个lchildrchildnullptr的结点,然后后面的结点直接使用rchild域进行连接,最后一个结点的rchildnullptr
    通常我们使用的先序、中序、后序遍历对于叶子结点来说都是从左到右的顺序,这了我们选择中序遍历。
    设置前驱结点指针pre,初始为空,第一个叶结点由指针head指向,遍历到叶结点时,就将它前驱的rchild指针指向它,最后一个叶结点的rchild为空,
    直接正常操作,然后在中间加入一个连接链表的操作即可
    一般有链表直接使用一个pre指针进行遍历比较快。
/*
void link_leaf(Bitree bt){
  int front=-1,rear=-1;
  Bitree tag = nullptr;
  int flag = 0;
  Bitree Queue[Maxsize];
  Quere[++rear] = bt;
  while(front!=rear){
    Bitree p = Queue[++front];
    if(p->lchild!=nullptr)
     Queue[++rear] = p->lchild;
    if(p->rchild!=nullptr)
     Queue[++rear] = p->rchild;
    if(p->lchild == p->rchild)
      { tag = p;break; flag = front;}
  }
  int i;
  for(i = front;Queue[i]!=nullptr;i++)
  {
     Queue[i]->rchild = Queue[i+1];
  }
  Queue[i-1]->rchild = nullptr;
}
*/
LinkedList head,pre;
LinkedList InOrder(Bitree bt){
   if(bt){
    InOrder(bt->lchild);
    if(bt->lchild==nullptr**bt->rchild==nullptr){
    if(pre==nullptr){
    head = bt;
    pre = bt;
   }else{
   pre->rchild = bt;
   pre = bt;
   }
   InOrder(bt->rchild);
   pre->rchild = nullptr; 
  }
}
return head;
}
  1. 试设计判断两棵二叉树是否相似的算法,所谓二叉树T1和T2相似,指的是T1和T2都是空的二叉树或都只有一个根结点;或T1的左子树和T2的左子树是相似的且T1的右子树和T2的右子树是相似的。
bool defer_similar(Bitree bt1, Bitree bt2){
  if(bt1==nullptr&&bt2==nullptr) return true;
  if((bt1->lchild == nullptr &&bt1->rchild==nullptr)&&(bt2->lchild==nullptr&&bt2->rchild==nullptr))
    return true;
  bool left = defer_similar(bt1->lchild,bt2->lchild);
  bool right = defer_similar(bt1->rchild,bt2->rchild);
  return (left&&right);
}
  1. 写出在中序线索二叉树里查找指定结点在后序的前驱结点的算法
    思路:(这种题一般都是分类讨论!从父结点和子结点的身份进行
    a/ 若结点p有右子女,则右子女是其前驱,若无右子女而有左子女,则左子女是其前驱。
    b/ 若结点p左右子女均无,设其中序左线索指向某祖先结点f(p是f右子树中按中序遍历的第一个结点),若f有左子女,则其左子女是结点p的在后序下的前驱
    c/ 若f无左子树,则顺其前驱找双亲的双亲,一直找到双亲有左子女(这时左子女是p的前驱)
    d/ 若p是中序遍历的第一个结点,结点p在中序和后序下均无前驱
ThreadNode* InPostPre(ThreadTree t,ThreadNode* p){
   ThreadTree q ;
   if(p->rtag == 0)//右子女存在直接就是左子女
      q = p->rchild;
   else if(p->ltag == 0)//只有左子女存在,前驱是左子女
     q = p->lchild;
   else if(p->lchild == nullptr)//是中序遍历的第一个结点,没有前驱返回nullptr
     q = nullptr;
   else{
     while(p->ltag == 1&& p->lchild!=nullptr)//顺着当前结点找其祖先,当ltag为1的时候当前结点是前驱
    { //一直找到一个有左孩子的结点,如果当前指向的是真正的左孩子结点,则返回该结点
       p = p->lchild;
     if(p->ltag == 0)
       q = p->lchild;
     else
       q = nullptr;
       }
 }
  return q;
}

typedef struct ThreadNode{
  Elemtype data;
  struct ThreadNode *lchild,*rchild;
  int ltag,rtag;
}ThreadNode,*ThreadTree;

//中序遍历建立中序线索二叉树
void InThread(ThreadTree &p,ThreadTree &pre){
  if(p!=nullptr)
   InThread(p->lchild,pre);
   if(p->lchild == nullptr)
   {
      p-lchild = pre;
      p->ltag = 1;
   }
   if(p != nullptr&&pre->rchild == nullptr)
   {
     pre->rchild = p;
     p->rtag =1 ;
   }
   pre = p;
   InThread(p->rchild,pre);
}
void create_InThread(ThreadTree t){
   ThreadTree pre = nullptr;
   if(t!=nullptr){
     InThread(t,pre);
     pre->rchild = nullptr;
     pre->rtag = 1;
  }
}



  1. 二叉树的带权路径长度(WPL)是二叉树中所有叶结点的带权路径长度之和。给定一棵二叉树T,采用二叉链表存储,结点结构:
left weight right 

其中叶结点的weight域保存该结点的非负权值。设root为指向T的根结点的指针,请设计求T的WPL的算法,要求:
(1)给出算法的基本设计思想
(2)给出二叉树结点的数据类型定义

//method one 
//二叉树结点的数据类型
typedef struct Node{
   int weight;//存储当前结点的权值
   struct Node* lchild,* rchild;
}*Bitree,BiNode;
//使用层次遍历需要的队列类型
typedef struct {
   Bitree Qu[Maxsize];
   int level[Maxsize];//存储每个结点的层数,每个结点的路经长度=层数-1
   int front=-1,rear = -1;
}Queue;

/*
使用二叉树的层次遍历,记录每个结点对应的路经长度值,直接使用Leaf结点的权重x对应的path长度
*/
int calculate_WPL(Bitree bt){
   Queue queue;
   int sum = 0;
   //Bitree flag = nullptr;
   if(bt==nullptr) return 0;
   if(bt->rchild==nulltpr&&bt->lchild == nullptr) return bt->weight;
   // k = 1;
   queue.Qu[++queue.rear] = bt;
   queue.level[queue.rear] = 1;
   while(queue.front!=queue.rear){
      Bitree p = queue.Qu[++front];
      if(p-lchild != nullptr)
      {
         queue.Qu[++rear]=p->lchild;
         queue.level[rear]=queue.level[front]+1;
      }
      if(p->rchild != nullptr)
      { 
         queue.Qu[++rear]=p->rchild;
         queue.level[rear]=queue.level[front]+1;
      }
      if(p->rchild==nullptr&&p->lchild==nullptr)
      {
          sum = sum + p->weight * (queue.level[front]-1);
      }
   }
    return sum;
}
//method two 层次遍历的另一种写法
int wpl_levelorder(bitree root){
 Bitree q[Maxsize];
 int end1,end2;
 end1= end2 = 0;
 int wpl = 0,deep =0;
 Bitree lastNode;
 Bitree newlastNode;
 lastNode = root;
 newlastNode = nullptr;
 q[end2++] = root;
 while(end1!=end2){
    Bitree t = q[end1++];
    if(t->lchild==nullptr&&t->rchild==nullptr){
    wpl+=deep*t->weight;
    if(t->lchild!=nullptr){
    q[end2++]=t->lchild;
    newlastNode = t->lchild;
    }
    if(t->rchild!=nullptr)
    {
     q[end2++]=t->rchild;
     newlastNode = t->rchild;
   }
   if(t==lastNode){
      lastNode = newlastNode;
      deep+=1;
    }
}
     
return wpl;
}

//method three 先序遍历
int wpl(bitree root){
 return wpl_PreOrder(root,0);
}
int wpl_preorder(bitree root,int deep){
   static int wpl =0;
   if(root->rchild==nullptr&&root->rchild==nullptr)
     wpl+=deep*root->weight;
   if(root->lchild!=nullptr)
     wpl+=wpl_preorder(root->lchild,deep+1);
   if(root->child!=nullptr)
     wpl+=wpl_preorder(root->rchild,deep+1);
   return wpl;
  
}

  1. 请设计一个算法,将给定的表达式树(二叉树)转换为等价的中缀表达式(通过括号反映操作符的计算次序)并输出。例如,当下列两棵表达式树作为算法的输入时:
    输出的等价中缀表达式分别为(a+b)*(c*(-d))和(a*b)+(-(c-d))
    二叉树结点定义如下:
typedef struct node{
  char data[10];
  struct node *left,*right;
}BiTree;

思路:可以基于二叉树的中序遍历策略得到所需的表达式。表达式树中分支结点对应的子表达式的计算次序,由该分支结点所处的位置决定。为得到正确的中缀表达式,需要在生成遍历序列的同时,在适当位置添加必要的括号。显然,表达式的最外层(对应根结点)及操作数(对应叶结点)不需要添加括号。
除了根结点和叶结点之外,遍历到其它结点在遍历其左子树之前加上左括号,在遍历其右子树之后加上右括号

void BtreetoE(BTree *root){
  BtreeToExp(root,1);//根的高度为1
}
void BtreeToExp(BTree *root,int deep){
  if(root == nullptr) return;
  else if(root->left == nullptr&&root->right==nullptr)
    cout<<root->data<<endl;
  else{//根结点通过deep=1来进行控制
   if(deep>1) cout<<"(";
   BtreetoExp(root->left,deep+1);
   cout<<root->data;
   BtreetoExp(root->right,deep+1);
   if(deep>1) cout<<")";
  }
}

tips:通过增加一个标识量来控制递归进行的某些条件,可能这个标识量没有特殊的意义但是在程序中起到控制数据流流动的功能,eg:上题的deep
19. 后序遍历
思路:因为后序递归遍历的二叉树顺序是先访问左子树,再访问右子树,最后访问根结点。当用堆栈来存储结点,必须分清返回根结点时,是从左子树返回的还是从右子树返回的。所以可以使用辅助指针r,其指向最近访问过的结点。也可以在结点中增加一个标志域,记录是否被访问。

void PostOrder(BiTree T){
   InitStack(S);
   p = T;
   r = nullptr;
   while(p||!IsEmpty(S)){
   //p如果为空说明当前结点已经指向叶子节点的子节点了,但是该遍历还是要遍历
   //而S栈为空则说明,当前的树遍历结束
    if(p){//所以这里面才可以有关于P空或者不空的if-else
    push(S,p);
    p=p->lchild;
    }
    else{
    GetTop(S,p);
    if(p->rchild&&p->rchild!=r){
    //先get到栈顶结点p,但是不知道栈顶结点是从左子树还是右子树得来的
    //所以有p->rchild!=r,r中存储着刚访问的结点,如果等于r的话说明刚从右子树过来,可以访问了
    //如果不是,将右结点压入堆栈
       p = p->rchild;
       push(S,p);
       p = p->lchild;
       }
    else{
     pop(S,p);
     visit(p->data);
     r=p;//但是r的改变是在访问完成一个结点之后的改变,因为根结点是最后一个
     p = nullptr;
 }
}
}

}

上面的思路利用:
当访问一个结点p时,栈中结点恰好是p结点的所有祖先结点,从栈底到栈顶结点再加上结点p就是一条从根结点到结点p的一条路径,求根结点到其他结点的路径和求两个结点的最近公共祖先都可以使用这个算法

  1. 在二叉树中查找值为x的结点,试编写算法,打印值为x的结点的所有祖先
typedef struct bitree{
    int data;
    struct bitree* lchild;
    struct bitree* rchild;
}*Bitree;
typedef struct  {
    Bitree t;
    int tag;
}stack;//使用tag表示返回到父节点时是从左子树返回还是从右子树返回
void search(Bitree bt,int x){
    stack s[Maxsize];
    int top =0;
    while(bt!=nullptr||top>0)
    {
        while(bt!=nullptr&&bt->data!=x)
        {
            s[++top].t = bt;
            //if(bt->lchild)实际上这里不需要判断同时也是直接赋空值给当前结点
            s[top].tag = 0;
            bt =  bt->lchild;
        }
        while(bt->data==x){
            for(int i=0;i<top+1;i++)
                cout<<s[i].t->data;
        }
        while(top!=0&&s[top].tag==1)
            top--;
        //回溯的过程,在栈中的数据如果是之前已经从右子树返回的但是已经经过data==x的判断
        //仍然在栈中出现的与当前要查找的结点路径无关,所以直接弹栈即可
        if(top!=0){
            s[top].tag=1;
            bt = s[top].t->rchild;
        }
    }
}
  1. 编程求以孩子兄弟链表为存储结构的森林的叶子结点数
  2. 以孩子兄弟链表为存储结构,请设计递归算法求树的深度
  3. 已知一棵树的层次序列以及每个结点的度,编写一个算法构造此树的孩子-兄弟链表
  4. 判断给定的二叉树是否是二叉排序树
  5. 求出指定结点在给定二叉排序树中的层次
  6. 利用二叉树的遍历思想编写一个判断二叉树是否是平衡二叉树的算法
  7. 求出给定二叉排序树中最小和最大的关键字
  8. 设计一个算法,从大到小输出二叉排序树中所有结点值不小于k的关键字
  9. 编写一个递归算法,在一棵有n个结点的随机建立起的二叉排序树上查找第k小的元素,并返回指向该结点的指针,要求算法的平均时间复杂度是O(log2n),二叉排序树中除了data,lchild,rchild等数据成员之外,增加一个count成员,保存以该结点为根的子树上的结点数。
  10. 输入一个整数data和一棵二元树。从树的根结点开始往下访问一直到叶结点,所经过的所有结点形成一条路经,打印出路径和与data相等的所有路径

你可能感兴趣的:(考研复习数据结构)