递归
文章目录
什么是递归
函数中存在着调用函数本身的情况。
递:将问题拆分为子问题解决,子问题再拆分子子问题……直到被拆解的问题无需再拆分为更细的子问题(可以开始往回求解)。
归:最小的子问题解决了,上一层的子问题解决了,上上一层子问题也就解决了……直到所有问题都被解决。
参考链接:https://www.cxyxiaowu.com/7259.html
递归算法通用解决思路
递归特点:
1、一个问题可以分解成具有相同解决思路的子问题,子子问题,也就是这些问题都能调用同一个函数,一般为函数自身
2、经过层层分解的子问题最后一定是有一个不能再分解的固定值(终止条件)向上进行返回
判断题目是否满足上述条件,使用递归?
递归解题套路:
1、定义一个函数,明确函数功能(递归的特点是问题和子问题都会调用函数自身)寻找问题与子问题之间的递归关系即可
2、寻找问题与子问题之间的关系(递推关系)由于问题与子问题具有相同的解决思路,子问题调用步骤1定义好的函数,自己调用自己,寻找最终不可再分解的子
问题的解【终止条件】确保子问题不会无限分解下去。
3、递推公式用代码表示出来,根据问题和子问题之间的关系,推导时间复杂度
注:树、二叉树题目通常使用递归解决,下面给出几个例子,自行纸上推导公式,递归结束条件
例题
1、反转二叉树/交换二叉树左右结点
分析:反转可以理解为交换二叉树的左右结点
第一步,定义一个函数,代表翻转以root为根的二叉树
第二步,查找问题与子问题之间的关系,得出递推公式,采用自上而下的思考方式,画图分析!
递推关系:翻转(根结点)= 翻转(根结点的左结点)+ 翻转(根结点右结点)
InvertTree(root)=InvertTree(root->left)+InvertTree(root->right)
第三步:递归终止的条件是当结点为叶子结点时终止【叶子结点没有左右结点】
TreeNode InvertTree(TreeNode root){
if(root==NULL) //遍历到叶子结点时,返回空
return NULL;
TreeNode temp;
//翻转左或者右结点下的左右结点
TreeNode left=InvertTree(root->left);
TreeNode right=InvertTree(root->right);
//对于根结点的左右结点进行交换
temp=root->left;
root->left=root->right;
root->right=temp;
return root;
}
2、二叉树高度
分析:函数功能,求以T为根的二叉树的高度;
递推关系,根结点高度=左边子树/右边子树的最大高度+1(根结点自身所在一层);
终止条件,叶子结点没有左右结点就终止,叶结点所在高度为1,但是左右孩子为空就返回高度为0
int TreeDepth(BiTree T){
if(T==NULL)
return 0;
LeftDepth=TreeDepth(T->lchild);
RightDepth=TreeDepth(T->rchild);
if(LeftDepth>RightDepth)
return LeftDepth+1;
else
return RightDepth+1;
}
3、二叉树宽度
分析:函数功能,求每一层最大的结点数【宽度】
递推关系,树的最大宽度【每一层计算结点数,存放在数组中,level为下标】
终止条件,叶子结点为空就返回空。
int count[100] = {0}; //全局数组
int max = 0; //宽度
Tree_width(pTree,count,1,max); //递归求树的最大宽度,初始树level为1,向下遍历每层+1
void Tree_Width(BiTree T,int level,int *count,int &max){
BiTree p=T;
if(p==NULL)
return ;
count[level]++; //count数组记录每一层结点数
if(max<count[level]) //max保存最大层结点
max=count[level];
Tree_Width(p->lchild,level+1,count,max); //每遍历一层,结点层次+1
Tree_Width(p->rchild,level+1,count,max);
}
4、二叉树双分支结点的个数
分析:函数功能,求某个结点是否为双分支结点=同时存在左孩子和右孩子
递推关系,根结点=左孩子+右孩子,左孩子=左左孩子+左右孩子……【单分支结点=存在左孩子/右孩子】
终止条件,遍历到叶子结点,返回0,没有左右孩子
例如:最左下的双分支,叶子结点都返回0,但自身是1个双分支结点
单分支结点不用自身+1,只要判断孩子结点中有无双分支
int Count_DNode(BiTree T){
BiTree p=T;
if(p=NULL) //叶子结点开始返回
return 0;
else if(p->lchild!=NULL&&p->rchild!=NULL) //双分支结点
return Count_DNode(p->lchild)+Count_DNode(p->rchild)+1;
else //单分支结点
return Count_DNode(p->lchild)+Count_DNode(p->rchild);
}
5、先序遍历第k个结点的值
分析:函数功能,每遍历一个结点进行计数直到第k个为止。终止条件,如果找到第k个结点的值就输出,如果找到叶子结点就返回NULL。
递归条件,左子树中没有找到的判断条件是遍历到左子树中最右下结点,然后在右子树中寻找
每一次递归表示遍历下一个结点,计数器加1 也就是之前都不成立,继续向后
int Search_kNode(BiTree T,int k){
BiTree p=T;
static int i=0; //对静态变量i进行计数,不需要随递归次数变化
if(p==NULL) //叶子结点返回-1
return -1;
if(i==k)
return p->data;
i++;
ch=Search_kNode(p->lchild,k);
if(ch!=-1) //左子树存在就输出,否则就右子树中找
return ch;
ch=Search_kNode(p->rchild,k);
}
6、删除以x为根的子树
分析:函数功能,删除以x为根的所有子树
递归条件,左右子树中都有以x为根的子树,如果左子树中有释放该结点,同理右子树
终止条件,叶子结点都不是就向上返回空
void Del_X(BiTree T,int x){
BiTree p=T;
if(p==NULL)
return ;
if(p->data==x)
free(p);
Del_X(p->lchild,x);
Del_X(p->rchild,x);
}
7、查找x的所有祖先,假设x唯一
分析:函数功能,查找值为x的所有祖先,也就是x的父结点,x的父结点的父结点……直到根结点【必为x的祖先】
递归条件,先找到值为x的结点,并标记已找到,可以向上返回;左右子树都需要找x,直到找到
终止条件,叶结点返回空;找到x就不用再找,否则继续找
注:一旦找到x后,flag就置为1,每次向上返回都是访问本次p的值,而不再进入递归中
void Search_Ancestor(BiTree T,int x){
BiTree p=T;
static int flag=0;
if(p==NULL) //叶节点就返回空
return ;
if(p->data==x)
flag=1;
if(flag==1) //找到x,开始向上返回
visit(p);
if(flag==0)
Search_Ancestor(p->lchild,x); //左子树中找完,flag==0 表示还要在右子树中找
if(flag==0)
Search_Ancestor(p->rchild,x);
}
8、查找p和q的最近公共祖先r
分析:函数功能,找p和q的公共祖先,也就是找到p的路径与找到q的路径类似,左右向上返回值都不为空
递归条件,没有找到p和q就继续找,返回值只有可能为NULL/p/q的值
终止条件,叶子结点都没有找到就返回空,直到找到p和q所在的结点为止,判断p和q的位置【画图分析】
BiTree Search_Ancestor(BiTree T,int x){
BiTree root=T,left,right;
if(root==NULL||root==p||root==q) //对于叶结点,p结点q结点返回值情况,递归终止条件
return root;
left=Search_Ancestor(root->lchild,x); //每个左右结点的返回值情况
right=Search_Ancestor(root->rchild,x);
//对于递归返回值判断
if(left==NULL&&right==NULL) //叶子结点的两侧都为空,不是祖先结点
return NULL;
if(left!=NULL&&right!=NULL) //p和q分别在root的异侧
return root;
if(left==NULL) //左结点为空,返回右边不为空的
return right;
if(right==NULL) //右结点为空,返回左边不为空的
return left;
}