12.寻找二叉树中值为x的节点(唯一),并打印出x的所有祖先
算法思想:后序非递归遍历的特点,当遍历到x结点时,此时栈中的结点都为x的祖先结点
typedef struct{
BiTree root;
int rTag;//各结点的右子树访问标记
}Stack[MaxSize];
void postOrder(BiTree root){
//声明并初始化栈数组
Stack S;
int top = -1;
while(top >= 0 || root){
//找值为x的结点
while(root && root->val != x){//沿左分支向下,将不等于x的结点全部压栈
S[++top].root = root;
S[top].rTag = 0;//将右孩子访问标记初始化
root = root->left;
}
//若找到值为x的结点,此时栈中结点全部为x的祖先结点,将其打印输出
if(root){
while(top > -1){
printf(S[top--].root->val);
}
return;//结束函数
}
//若沿左分支向下没有找到x,则从栈中结点的右孩子沿左分支向下寻找
while(top > -1){//从栈中找到有未被访问的右孩子结点,否则将其出栈
if(S[top].root->right && S[top].rTag == 0){//该结点有右孩子且未被访问
root = S[top].root->right;
S[top].rTag = 1;
}else{
top--;//将没有右孩子或者已经访问过右孩子的结点出栈
}
}
}
}
13.找二叉树中任意两个结点的最近公共祖先r
①深度优先遍历(后序遍历)
算法思想:任意两个结点最近的公共祖先r有二种情况:
(1)两个结点在某一个结点的左右子树上,则该结点就是这两个结点的最近公共祖先
(2)这两个结点其中一个结点在以另一个结点为根的子树上,则为根的结点就是这两个结点的最近公共祖先
bool postOrder(BiTree root,BiTree p,BiTree q,BiTree r){
if(!root){//空结点返回false,代表无p,q结点
return false;
}
//在当前根结点的左子树上找p或q结点,若找到,返回true,没找到返回false
bool lchild = (root->left,p,q,r);
//在当前根结点的右子树上找p或q结点,若找到,返回true,没找到返回false
bool rchild = (root->right,p,q,r);
//①如果当前根结点的左右子树上有p,q结点,则该结点就是p,q的最近公共祖先
//②如果当前根结点就是p或q结点,且其子树上有q或p结点,则该结点就是p,q的最近公共祖先,由于
//是自下向上遍历,r的最终值必是祖先结点
if((lchild && rchild) || root->val == p->val || root->val == q->val){
r = root;
}
//若当前根结点的左子树或右子树包含p或q结点,或当前根节点就是p或q结点,返回true
return lchild || rchild || root->val == p->val || root->val == q->val;
}
BiTree lowestCommomAncestor(BiTree root,BiTree p,BiTree q){
BiTree r = NUll;
postOrder(root,p,q,r);//调用递归函数
return r;//返回最近的公共祖先结点
}
②后序非递归遍历
算法思想:由于后序非递归遍历的特点,当遍历到p或q结点时,此时栈中的结点都为p或q的祖先结点,用栈分别保存p,q的祖先结点,再从上至下一一比较,第一个相同的结点即是p,q的最近公共祖先结点
typedef struct{
struct TreeNode* curr;
int tag;
}Stack[MaxSzie];
void postOrder(BiTree root, BiTree p, BiTree q,BiTree r) {
Stack stack;
Stack s1,s2;//辅助栈s1,s2分别存储p和q的祖先结点
int top = -1;
int top1=-1,top2=-1;//s1存储p的祖先结点(包括自己),s2存储q的祖先结点(包括自己)
while(root || top != -1){
while(root){//将沿着root一直向左的结点进栈,直至为空
stack[++top].curr = root;
stack[top].tag = 0;
root = root->left;
}
//栈不为空且栈顶结点的右子树的已访问
while(top != -1 && stack[top].tag == 1){
//当栈顶结点为p时,将栈中元素复制到s1栈中
if(stack[top].curr == p){
for(int i = 0;i <= top;i++){
s1[i].curr = stack[i].curr;
}
top1 = top;
}
//当栈顶结点为q时,将栈中元素复制到s2栈中
if(stack[top].curr == q){
for(int i = 0;i <= top;i++){
s2[i].curr = stack[i].curr;
}
top2 = top;
}
//当p,q的祖先结点均找到时,从s1,s2的栈顶向下一一比较是否有相同的结点
if(top1 != -1 && top2 != -1){
for(int i = top1;i>=0;i--){
for(int j = top2;j>=0;j--){
if(s1[i].curr == s2[j].curr){
r = s1[i].curr;//返回最近的公共祖先结点
}
}
}
}
top--;//出栈
}
if(top != -1){
stack[top].tag = 1;
root = stack[top].curr->right;//沿栈顶结点的右子树向下遍历
}
}
}
BiTree lowestCommonAncestor(BiTree root, BiTree p, BiTree q){
BiTree r = NULL;
postOrder(root,p,q,r);
return r;
}
14.求二叉树b的宽度(具有结点数最多的那一层的结点个数)
算法思想:层次遍历二叉树,用数组记录下每一个结点的所在层次,遍历层次数组,层次数最多的即为节点数最多的层次,即二叉树的宽度。
int BiTWidth(BiTree root){
//定义并初始化队列
BiTree val[MaxSize];
int front = rear = -1;
int level[Maxsize];//记录结点的层次
//将根结点入队并记录其所在层次
val[++rear] = root;
level[rear] = 1;
BiTree temp = NULL;//保存出栈结点
int k;//保存出栈结点层次
//层次遍历记录各结点的层次
while(front != rear){
temp = val[++front];
k = level[front];
if(temp->left){//如果出栈结点有左孩子,将左孩子入队并记录其层次
val[++rear] = temp ->left;
level[rear] = k+1;
}
if(temp->right){//如果出栈结点有左孩子,将左孩子入队并记录其层次
val[++rear] = temp ->right;
level[rear] = k+1;
}
}
//遍历层次数组,找最多层次数
int levalNumverMax = 0;
int k = 1;//层次数从1开始
for(int i = 0;i <= rear;i++){
int count = 0;//记录每一个层次的数量
while(level[i] == k && i <= rear){
count++;
i++;
}
if(count > levelNumberMax){
levelNumberMax = count;
}
k = level[i]; //遍历下一层次
}
return levelNumberMax;//返回最多层次数
}
15.一颗满二叉树的结点值均不同,已知前序遍历序列pre,设计一个算法求其后序序列post
算法思想:二叉树的前序转为后序的步骤是将前序的左子树序列转化为后序的左子树序列,前序的右子树序列转化为后序的右子树序列,前序的根转化后序的根,即将子树的第一个结点转化为其最后一个结点(根结点),显然是个递归过程,由于是满二叉树,左右子树上的结点数相同,即在前序数组中的左右子树存储的长度相同。
void preToPost(ElemType pre[],int f1,int r1,ElemType post[],int f2,int r2){
if(r1 >= f1){//递归出口,若为空子树时
post[r2] = pre[f1];//将当前子树前序的第一个结点转化为后序的最后一个结点(根结点)
int mid = (r1-f1)/2;//取相对f1的中间位置
preToPost(pre,f1+1,f1+mid,post,f2,f2+mid-1);//将前序左子树转化
preToPost(pre,f1+mid+1,r1,post,f2+mid,r2-1);//将前序右子树转化
}
}
16. 将二叉树的叶子结点从左到右的顺序连接为一个单链表,表头指针为head,用叶结点的右指针/域存放单链表指针
算法思想:维护一个指向上一个访问叶子结点的指针,中序遍历遍历到叶子结点,让上一个访问的叶子结点的右指针指向当前叶子结点
BiTree pre = NULL;//指向上一个访问的叶子结点
BiTree head = NULL;//叶子结点链表的头指针
BiTree leavesNode_Link(BiTree root){
if(!root){
return NULL;
}
leavesNode_Link(root->left);
if(pre){
if(root->left == NULL && root->right == NULL){//如果当前结点为叶子结点
pre->right = root;
pre = root;
}
}else{//给pre和head赋初值,让其指向第一个叶子结点
pre = root;
head = root;
}
leavesNode_Link(root->right);
return head;
}
17.判断两棵二叉树是否相似,T1和T2相似是指T1和T2都是空树或只有根结点,或T1与T2的左子树是相似的,或T1与T2的右子树是相似的
算法思想:判断两棵树相似即T1和T2的左右子树都相似,而判断左右子树相似,也是同样的方法,显然是个递归过程
bool twoTreeIsSame(BiTree root1,BiTree root2){
if(!root1 && !root2){//两棵树都是空树时,相似
return true;
}else if(root1 || root2){//只有其中一棵树为空树,不相似
return false;
}else{//两棵树都不为空树时,看左右孩子是否相似
return twoTreeIsSame(root1->left,root2->left) && twoTreeIsSame(root1->right,root2->right);
}
}
18.在中序线索二叉树里查找指定结点在后序序列中的前驱结点的算法
算法思想:
(1)当该指定节点有孩子节点时
①有右孩子节点,其前驱即是该右孩子节点
②有左孩子节点,无右孩子节点,其前驱既是该左孩子节点
特殊情况:当节点为中序序列的第一个节点,即是后序序列中的第一个节点,无前驱节点
(此时状态为左指针为线索,但指针为空)
(2)当该指针节点无孩子节点时
①若该节点的前驱节点有左孩子时,则该左孩子即为该节点的前驱节点
原因:因为在中序序列中,该节点的前驱节点必是该节点所在右子树的根节点,而该节点为右子树最左下节点(中序序列中右子树的第一个节点),同时该节点也是后序序列中右子树的第一个节点,则该根节点的左孩子节点是该节点的前驱节点
②若该节点的前驱节点无左孩子节点时,重复此操作,沿着左线索寻找有左孩子的前驱节点,或该前驱节点为中序序列的第一个节点为止(此时左子树为空,无前驱节点)
typedef struct{
struct ThreadBiNode* left,right;
int ltag,rtag;
ElemType val;
}ThreadBiNode,*ThreadBiTree;
ThreadBiTree InPostThreadTree_SearchPreNode(ThreadBiTree root,ThreadBiTree p){
//①p有孩子时
if(p->rtag == 0){//p有右孩子
return p->right;
}else if(p->ltag == 0){//p没有右孩子但有左孩子时
return p->left;
}else if(p->left == NULL){//p没有孩子时,左孩子是线索但左孩子是空时,p为中序(后序)序列的第一个节点,无前驱
return NULL;
}
//②p无孩子时,沿着左线索寻找有左孩子的前驱节点
while(p->ltag == 1 && p->left){//有左孩子时或左指针为线索但为空时结束循环
p = p->left;
}
if(p->ltag == 0){//有左孩子的前驱节点
return p->left;
}else{//左子树为空,无前驱节点
return NULL;
}
}
19.求一颗二叉树root的带权路径长度,数据域存储的是非负权值
算法思想:设根节点的层次为0,则叶子节点的所在层次数即为根节点到叶子节点路径上的边数,带权路径长度为各叶子节点的权值与路径边数乘积的和
void WPL_preOrder(BiTree root,int deepth,int* WPLValue){
if(!root){
return;
}
if(root->left == NULL && root->right == NULL){//找到叶子节点,将权值加到结果和中
*WPLValue += root->val * deepth;
}
//递归左右子树,寻找叶子节点,同时每向下一层,层次加1
WPL_preOrder(root,deepth+1,WPLValue);
WPL_preOrder(root,deepth+1,WPLValue);
}
int BiT_WPL(BiTree root){
WPLValue = 0;
deepth = 0;
WPL_preOrder(root,0,&WPLValue);
return WPLValue;
}
20.将给定的表达式树转化为等价的中缀表达式
算法思想:该表达式树分为三种节点
①根节点:根节点中运算符的运算最外层不需要加括号。
②叶子节点:叶子节点为操作数,各个操作数外面不需要加括号。
③除根以外的非叶节点:该种节点中的运算符的运算,需要在参与运算的表达式两侧加括号。
将该表达式转化为等价的中缀表达式,等于先将左右子树转化为中缀表达式,再将当前根节点转化得到最终中缀表达式,显然这是个递归过程,根据深度数判断是否是根节点
void Exchage(BiTree root,int deepth){
if(!root){
return;
}
if(root->left == NULL && root->right == NULL){//操作数节点,直接输出
printf(root->val);
}else{//非叶节点
if(deepth > 1){//非根非叶节点,运算前先输出左括号
printf('(');
}
post_Exchange(root->left,deepth+1);//递归左子树寻找操作数
printf("%s",root->val);//输出运算符
post_Exchange(root->right,deepth+1);//递归右子树寻找操作数
if(deepth > 1){
printf(')');//运算后输出右括号
}
}
}
void exchange(BiTree root){
post_Exchange(root,1);//设根节点的深度为1
}