递归函数的思路
很多人觉得递归函数很绕,不知道怎么写,其实只是没掌握思路,一旦你了解了递归函数的思路,写递归就是一件水到渠成的事情了。
我自己写递归函数一般分两步:
- 缩小问题的规模,并且把规模缩小到一个单位的规模,并且解决掉第一个单位
增加检查条件
其实就是这么简单的两步,多说无益,直接看题。1. 累加
1+2+3+····+n
- 累加就是一个非常简单的递归例子
- 首先把问题缩小到一个单位的规模,这里就是一个数,并且选择第一个单位,就是1
public int sum(int n) {
// 把问题缩小到一个单位的规模
if(n == 1){
return 1;
}
// 调用递归
return sum(n - 1) * n;
}
- 测试用例
/**
* 测试用例
* @param args
*/
public static void main(String[] args) {
Demo demo = new Demo();
System.out.println(demo.sum(5));
System.out.println(demo.mulSum(5));
}
输出:
15
120
2. 根据list创建链表
- 首先我们先写一个链表的数据结构
/**
* 节点类
*
*/
public class Node {
private final int value; // 本结点的值
private Node next; // 下一个结点
public Node(int value, Node next) {
this.value = value;
this.next = null;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public int getValue() {
return value;
}
/**
* 打印方法
* @param head
*/
public static void printLinkedList(Node head) {
while(head != null) {
System.out.print(head.getValue());
System.out.print(" ");
head = head.getNext();
}
System.out.println();
}
}
- 接下来就是递归来根据 list 生成链表
- 思路还是一样的,先把问题规模缩小到一个单位,就是链表的一个结点,并且解决掉第一个单位,就是解决掉链表的第一个结点
/**
* 生成链表
* @param data
* @return 返回头结点
*/
public Node createLinkedList(List<Integer> data) {
// 把问题缩小到链表的一个结点,在解决掉链表的第一个结点
// 这里链表的第一个结点值就是 list 第一个值,就是data.get(0),调用构造函数去生成第一个结点
Node first = new Node(data.get(0), null);
// 递归生成接下来的所有结点
Node headOfSubList = createLinkedList(data.subList(1, data.size()));
// 将第一个结点的引用指向下一个结点
first.setNext(headOfSubList);
return first;
}
- 做完第一步其实就已经是完成递归了,接下来就是增加条件检查,这里调用了data.get(0),所以应该去对data为空的情况下,做一个检查
/**
* 生成链表
* @param data
* @return 返回头结点
*/
public Node createLinkedList(List<Integer> data) {
// 检查 data 是否为空
if(data.isEmpty()) {
return null;
}
Node first = new Node(data.get(0), null);
Node headOfSubList = createLinkedList(data.subList(1, data.size()));
first.setNext(headOfSubList);
return first;
}
- 测试用例
/**
* 测试用例
* @param args
*/
public static void main(String[] args) {
Recursion rec = new Recursion();
Node.printLinkedList(rec.createLinkedList(new ArrayList<>()));
Node.printLinkedList(rec.createLinkedList(Arrays.asList(1)));
Node.printLinkedList(rec.createLinkedList(Arrays.asList(1, 2, 3, 4, 5)));
}
输出:
1
1 2 3 4 5
3. 递归删除指定结点
- 假设我们有一条链表,我们传一个参数进去,利用递归删除掉这个结点。就是12345,传个2,就变成1345的链表
- 思路还是一样,首先我们应该把问题规模缩小到一个单位,也就是链表的头结点,然后对头结点进行操作,假如这个结点不是我们要删除的结点,我们就保留它,假如是我们要删除的,我们就把头结点赋值给下一个结点
/**
* 递归删除指定结点
* @param head 头结点
* @param N 要删除的值
* @return 头结点
*/
public Node deleteNode(Node head, int N) {
// 可能连续几个结点都是我们要删除的值,所以我们这里用一个 while 循环,而不是一个 if 判断
// 当头结点就是我们要删除的结点,我们就把下一个结点赋值给头结点
while(head.getValue() == N) {
head = head.getNext();
}
// 递归生成所有结点
head.setNext(deleteNode(head.getNext(), N));
return head;
}
- 接下来,就是增加条件判断,代码中调用了 head.getValue() 和 head.getNext(),所以我们应该对 head 做一个非空判断
/**
* 递归删除指定结点
* @param head
* @param N
* @return
*/
public Node deleteNode(Node head, int N) {
if(head == null) {
return null;
}
while(head.getValue() == N) {
head = head.getNext();
if(head == null)
return null;
}
head.setNext(deleteNode(head.getNext(), N));
return head;
}
-- 测试用例
/**
* 测试用例
* @param args
*/
public static void main(String[] args) {
Recursion rec = new Recursion();
Node.printLinkedList(rec.deleteNode(rec.createLinkedList(Arrays.asList(2, 2, 1, 2, 2, 3, 4, 5)), 2));
Node.printLinkedList(rec.deleteNode(rec.createLinkedList(new ArrayList<>()), 2));
}
输出:
1 3 4 5
看完这几个例子,其实就不难发现,只要理解的递归的思路,其实递归函数是非常容易写的,并且相当,简单粗暴。
递归的缺点
递归函数简单易理解,也相对暴力,所以也有显而易见的缺点,简单来讲,就是我们每次调用一个方法,其实就像向虚拟机栈里面压一个栈帧,所以如果我们如果递归调用的层数太多,像是我们要生成一个一百万个结点的链表,这时候就是报异常了,就是 StackOutFlowError 堆栈溢出异常。
为了解决这个问题,我们也可以根据实际的情况,将递归函数改造成 while 循环的方法,这样就只调用一次方法,就不会有 StackOutFlowError 的情况了。这里举个例子
循环删除链表的结点
问题还是删除链表的结点,只是这里用 while 来代替递归函数。我这里用 curNode 作为一个游标,来表示现在检查到的结点,如果我们检查到 curNode 的下一个结点是我们要删除的,我们就把 next 指向再下一个结点。
/**
* while 循环删除结点
* @param head 头结点
* @param value 要删除的值
* @return 头结点
*/
public Node deleteNodeByLoop(Node head, int value) {
if(head == null) {
return null;
}
// 处理头结点重复的情况
while(head.getValue() == value) {
head = head.getNext();
if(head == null){
return null;
}
}
// 处理头结点之后的结点
Node curNode = head;
while(curNode.getNext() != null) {
if(curNode.getNext().getValue() == value) {
curNode.setNext(curNode.getNext().getNext());
} else {
curNode = curNode.getNext();
}
}
return head;
}
写在最后
源码在这里
还写了链表反转的递归写法和 while 写法,可以了解一下