边儿上的小人您要嫌碍事儿,可将鼠标放在上头,边上底下有个叉,点一下就收起来了

编码技巧之递归【笔记】

编码技巧之递归【笔记】

把想法用程序写出来是很重要的

使用数学归纳法的思想来进行编程

首先要明白数学归纳法怎么用,数学归纳法是用于证明断言对所有自然数成立,首先证明对于n=1成立,然后证明n>1时:如果对于n-1成立,那么就对于n成立

那么对整个过程进行程序化我们就可以得到

递归控制

如何证明递归函数正确执行?

使用数学归纳法中的数学/自然语言,然后将其变成程序语言

递归书写的方法

严格定义递归函数作用,包括参数,返回值,side-effect

先写一般的情况,然后再写特殊的情况

每次调用必须缩小问题的规模,这是很重要的,不然就可能进入死循环

而且每次问题缩小的规模程序必须设为1

例题一:链表的创建

给出一个数组,将数组的每一个元素都生成一个节点,将节点首尾相接,还有两点,第一,链表必须以null结尾,第二,必须将第一个节点返回作为链表头

基础Node.java

代码如下:

package interview.common;

public class Node<T> {
  private final T value;
  private Node<T> next;

  public Node(T value) {
    this.value = value;
    this.next = null;
  }

  public T getValue() {
    return value;
  }

  public Node<T> getNext() {
    return next;
  }

  public void setNext(Node<T> next) {
    this.next = next;
  }

  public static <T> void printLinkedList(Node<T> head) {
    while(head != null) {
      System.out.print(head.getValue());
      System.out.print(" ");
      head = head.getNext();
    }
    System.out.println();
  }
}

具体代码如下:

package interview.recursion;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import interview.common.Node;

public class LinkedListCreator {

  /**
   * Creates a linked list.
   *
   * @param data the data to create the list
   * @return head of the linked list. The returned linked list
   * ends with last node with getNext() == null.
   */
public <T> Node<T> createLinkedList(List<T> data) {
	if (data.isEmpty()) {
	      return null;
	 }

    Node<T> firstNode = new Node<>(data.get(0));
    firstNode.setNext(
        createLinkedList(data.subList(1, data.size())));
    return firstNode;
  }

  public Node<Integer> createLargeLinkedList(int size) {
    Node<Integer> prev = null;
    Node<Integer> head = null;
        for (int i = 1; i <= size; i++) {
      Node<Integer> node = new Node<>(i);
      if (prev != null) {
        prev.setNext(node);
      } else {
        head = node;
      }
      prev = node;
    }
    return head;
  }

  public static void main(String[] args) {
    LinkedListCreator creator = new LinkedListCreator();

    Node.printLinkedList(
        creator.createLinkedList(new ArrayList<>()));
    Node.printLinkedList(
        creator.createLinkedList(Arrays.asList(1)));
    Node.printLinkedList(
        creator.createLinkedList(Arrays.asList(1, 2, 3, 4, 5)));
  }
}

例题二:链表反转

将例题一的链表反转过来

我们假设除了一以外的这个链表可以正确的反转,反转以后需要将一想办法加入其中,只要将一和二的位置换一下就行了

具体代码如下:

package interview.recursion;

import java.util.ArrayList;
import java.util.Arrays;

import interview.common.Node;

public class LinkedListReverser {

  /**
   * Reverses a linked list.
   *
   * @param head the linked list to reverse
   * @return head of the reversed linked list
   */
  public <T> Node<T> reverseLinkedList(Node<T> head) {
    // size == 0 or size == 1
    if (head == null || head.getNext() == null) {
      return head;
    }

    Node<T> newHead = reverseLinkedList(head.getNext());
    head.getNext().setNext(head);
    head.setNext(null);
    return newHead;
  }

  public static void main(String[] args) {
    LinkedListCreator creator = new LinkedListCreator();
    LinkedListReverser reverser = new LinkedListReverser();

    Node.printLinkedList(reverser.reverseLinkedList(
        creator.createLinkedList(new ArrayList<>())));

    Node.printLinkedList(reverser.reverseLinkedList(
        creator.createLinkedList(Arrays.asList(1))));

    Node.printLinkedList(reverser.reverseLinkedList(
        creator.createLinkedList(Arrays.asList(1, 2, 3, 4, 5))));

    System.out.println("Testing large data. Expect exceptions.");
    reverser.reverseLinkedList(
        creator.createLargeLinkedList(1000000));
    System.out.println("done");
  }
}

例题三:列出所有组合

将数组中的所有数字进行任意组合,其中包含n个数,全部输出

要点:多个参数的初始值以及side-effect的维护

具体代码如下:

package interview.recursion;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Combinations {

  /**
   * Generates all combinations and output them,
   * selecting n elements from data.
   */
  public void combinations(
      List<Integer> selected, List<Integer> data, int n) {
    if (n == 0) {
      // output all selected elements
      for (Integer i : selected) {
        System.out.print(i);
        System.out.print(" ");
      }
      System.out.println();
      return;
    }

    if (data.isEmpty()) {
      return;
    }

    // select element 0
    selected.add(data.get(0));
    combinations(selected, data.subList(1, data.size()), n - 1);

    // un-select element 0
    selected.remove(selected.size() - 1);
    combinations(selected, data.subList(1, data.size()), n);
  }

  public static void main(String[] args) {
    Combinations comb = new Combinations();

    System.out.println("Testing normal data.");
    comb.combinations(
        new ArrayList<>(), Arrays.asList(1, 2, 3, 4), 2);
    System.out.println("==========");
  }
}

递归的缺点

简单来说就是stack,每一次递归调用都会将函数放进调用堆栈,调用的开销很大,可能导致stack overflow

这样会让时间和空间消耗比较大

同时递归本质是把一个问题分解为多个问题,很容易出现重复的运算

而且递归还存在栈溢出的情况,我们知道进程的栈容量都是有限的,递归需要堆栈,所以空间消耗要比非递归代码要大

但是也不要尝试将递归变成非递归,因为一般化的方法都是需要用到栈的,并且代码复杂,同时很难从根本上解决问题

posted @   DbWong_0918  阅读(118)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示