为什么要用递归以及递归如何书写?

一.为什么要用递归

一般情况下我们的程序是用递推,一步一步往下写,这样也符合我们思考问题的方式。那上面情况下要用递归呢?就是当数据或者问题不是线性而是树状或者图状的时候。

就拿我第一次遇见递归的爬楼梯问题举例,是一道非常经典的题目。

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

比如是3,我们每次爬有两种情况,爬一次,爬两次,然后每次爬完下次爬也会面临多次选择,有多少种情况我们是没办法事先知道的,所以这就是一种树的模型。

如果用递推并不是没法写,可以用队列模拟,先将对后序有影响的情况推入队列,等到与情况相关的子情况遍历完再排出队列。

但是这种树的问题,最适合的是用递归来解决。递归就是模拟我们画这颗树解决问题的过程,更加形象直观,便于理解。

当然,递归不是万能的,不要任何事情都用递归,如果是线性问题,就不要用递归。

解决线性问题的时候,首先递归通常伴有重复的计算加上很多的函数调用,所以时间复杂度通常比递推慢。就算加入动态规划,使得时间复杂度勉强达到一个层次,动态规划的数组与函数调用时需要保存的状态所需的空间复杂度肯定更高。最重要的是思路还变得更为麻烦。

综上所述,递归适用于树状或者图状的问题/数据。

二.递归怎么书写?

例题:给定一棵树的前序遍历数组,判断这棵树是不是二叉搜索树。

第一步:首先你分析问题,要有递归的思路,知道要递归什么来解决问题。

比如上面这个通过前序遍历判断搜索二叉树,首先我们要清楚二叉搜索树的定义。

根据定义,我们不难得出思路,先判断这颗二叉树的左子树(不为空的话)的所有结点是不是小于它的根结点,再判断右子树(不为空的话)的所有结点是不是大于它的根结点。

然后递归判断其左右子树(如果有的话)是不是也满足这个条件。

另外的话你也要清楚前序遍历的特点,第一个结点一定为根节点,然后的话遍历顺序为根结点->左子树->右子树。

结合搜索二叉树,我们可以得出第一个点为根结点,然后往后找,第一个比根结点小的树为左子树的根结点,第一个比根节点大的为右子树的根节点,左子树根节点与右子树根结点之间为左子树的前序遍历序列,右子树之后为右子树的前序遍历序列。

我们只需要看左子树的序列是不是都比根结点的数据小,右子树的序列是不是都比根节点的数据大。然后的话递归遍历左右子树的前序遍历序列看看是不是符合这个特点。

第二步:先按照思路(第一层)写出函数的定义与函数体

一般的话写函数都是先确定函数的定义与输入,然后按照思路写输出。

而递归函数的话我认为要先写思路和定义再写输入,或者先写部分输入,然后写完思路后再过来修改。

bool isBST() {
	int rootData=tree[leftBound].data;
	//寻找第一个比根结点小的数是左子树的根结点,
	//寻找第一个比根结点大与或等于的数即时右子树的根节点
	int leftRoot=-1,rightRoot=-1;
	for(int i=1; i<=n-1; i++) {
		if(tree[i].data<rootData&&leftRoot==-1) {
			leftRoot=i;
		} else if(tree[i].data>=rootData&&rightRoot==-1) {
			rightRoot=i;
			break;
		}
	}
    bool haveLeft=(leftRoot!=-1);//是否存在左子树
	bool haveRight=(rightRoot!=-1);//是否存在右子树
    //	根据左子树根结点和右子树根结点即可划分出左右子树
    //	遍历左子树,看看是不是都比根结点小(如果有的话)
	if(haveLeft) {
		for(int i=leftRoot; i<=rightRoot-1; i++) {
			if(rootData<=tree[i].data) {
				return false;
			}
		}
	}
    //遍历右子树,看看是不是都比根结点大(如果有的话)
	if(haveRight) {
		for(int i=rightRoot; i<=0; i++) {
			if(rootData>tree[i].data) {
				return false;
			}
		}
	}

	//子树也要满足是二叉搜索树(如果有的话)
	if(haveLeft&&haveRight) {
        //左子树
		if(!isBST())
			return false;
        //右子树
		if(!isBST())
			return false;
	} else if(haveLeft&&!haveRight) {
		if(!isBST())
			return false;
	} else if(!haveLeft&&haveRight) {
		if(!isBST())
			return false;
	}

	return true;
}

第三步:根据函数的定义与函数体进一步确定需要的参数,并修改原来的代码

上面的话我们的话我们肯定需要有一个tree数组代表数,另外的话我们上面的遍历是遍历整棵树,是从0到n-1,但是为了这个函数能够复用,所以我们需要传入leftBound和rightBound,即数组的左界和有界来确定一颗树。

添加参数并修改代码:

bool isBST(TreeNode tree[],int leftBound,int rightBound) {
	int rootData=tree[leftBound].data;
	//寻找第一个比根结点小的数是左子树的根结点,
	//寻找第一个比根结点大与或等于的数即时右子树的根节点
	int leftRoot=-1,rightRoot=-1;
	for(int i=leftBound+1; i<=rightBound; i++) {
		if(tree[i].data<rootData&&leftRoot==-1) {
			leftRoot=i;
		} else if(tree[i].data>=rootData&&rightRoot==-1) {
			rightRoot=i;
			break;
		}
	}
//	根据左子树根结点和右子树根结点即可划分出左右子树
//	遍历左子树,看看是不是都比根结点小
	bool haveLeft=(leftRoot!=-1);
	bool haveRight=(rightRoot!=-1);
	if(haveLeft) {
		for(int i=leftRoot; i<=rightRoot-1; i++) {
			if(rootData<=tree[i].data) {
				return false;
			}
		}
	}
	if(haveRight) {
		//遍历右子树,看看是不是都比根结点大
		for(int i=rightRoot; i<=rightBound; i++) {
			if(rootData>tree[i].data) {
				return false;
			}
		}
	}

	//子树也要满足是二叉搜索树
	if(haveLeft&&haveRight) {
		if(!isBST(tree,leftRoot,rightRoot-1))
			return false;
		if(!isBST(tree,rightRoot,rightBound))
			return false;
	} else if(haveLeft&&!haveRight) {
		if(!isBST(tree,leftRoot,rightBound))
			return false;
	} else if(!haveLeft&&haveRight) {
		if(!isBST(tree,rightRoot,rightBound))
			return false;
	}

	return true;
}

第四步:最后还要设定最后一层递归的终止条件,以免一直循环下去。

这题的话最终的话循环到最后的树一定就只剩下一个点,一个点的话肯定为搜索二叉树。

添加终止条件完成程序。

bool isBST(TreeNode tree[],int leftBound,int rightBound) {
	if(leftBound==rightBound) {
		return true;
	}
	int rootData=tree[leftBound].data;
	//寻找第一个比根结点小的数是左子树的根结点,
	//寻找第一个比根结点大与或等于的数即时右子树的根节点
	int leftRoot=-1,rightRoot=-1;
	for(int i=leftBound+1; i<=rightBound; i++) {
		if(tree[i].data<rootData&&leftRoot==-1) {
			leftRoot=i;
		} else if(tree[i].data>=rootData&&rightRoot==-1) {
			rightRoot=i;
			break;
		}
	}
//	根据左子树根结点和右子树根结点即可划分出左右子树
//	遍历左子树,看看是不是都比根结点小
	bool haveLeft=(leftRoot!=-1);
	bool haveRight=(rightRoot!=-1);
	if(haveLeft) {
		for(int i=leftRoot; i<=rightRoot-1; i++) {
			if(rootData<=tree[i].data) {
				return false;
			}
		}
	}
	if(haveRight) {
		//遍历右子树,看看是不是都比根结点大
		for(int i=rightRoot; i<=rightBound; i++) {
			if(rootData>tree[i].data) {
				return false;
			}
		}
	}

	//子树也要满足是二叉搜索树
	if(haveLeft&&haveRight) {
		if(!isBST(tree,leftRoot,rightRoot-1))
			return false;
		if(!isBST(tree,rightRoot,rightBound))
			return false;
	} else if(haveLeft&&!haveRight) {
		if(!isBST(tree,leftRoot,rightBound))
			return false;
	} else if(!haveLeft&&haveRight) {
		if(!isBST(tree,rightRoot,rightBound))
			return false;
	}

	return true;
}

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值