zoukankan      html  css  js  c++  java
  • 编码技巧之递归【笔记】

    编码技巧之递归【笔记】

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

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

    首先要明白数学归纳法怎么用,数学归纳法是用于证明断言对所有自然数成立,首先证明对于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

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

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

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

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

    感谢观看,文笔有限,博客不出彩,还请多多见谅
  • 相关阅读:
    第一章:进销存系统基本功能
    SpringBoot 整合 Docker
    Java的脚本机制、编译器API
    Java 定时任务
    监听文件修改的四种方法
    SpringBoot Actuator — 埋点和监控
    Kafka消息队列
    OpenSSL配置HTTPS
    Java 国际化
    备忘录模式
  • 原文地址:https://www.cnblogs.com/jokingremarks/p/14465886.html
Copyright © 2011-2022 走看看