编程思想(技巧)---递归控制

来源:互联网 发布:诺查丹玛斯 知乎 编辑:程序博客网 时间:2024/06/07 23:58

最近在看谷歌面试官讲解的视频,特来分享一波~ 这个系列主要是讲编程思想(或者说技巧),主要包括:递归控制、循环控制、边界控制和一些数据结构的知识。这一节主要说下递归。

大佬们讲课很深奥,首先说了下数学归纳法,并用典型的求和的递推公式:首项加尾项乘以项数除以2,即n(n+1)/2讲解了数学归纳法,这也是递归思想的一部分理论基础。朋友们可以看看这些思想~ 下面说了递归的知识:

(1). 如何保证递归函数正确执行?

使用数学归纳法/自然语言,推纳出来之后转化为程序语言。

(2). 递归书写方法(套路)

  • 严格定义递归函数作用,包括参数,返回值,side-effect
  • 先一般,后特殊
  • 每次调用必须缩小规模
  • 每次问题规模缩小程度必须为1

根据上述套路我们就可以写出一个递归函数,下面举例说明:

例子1:链表创建,返回头节点,链表尾部指向null

描述:使用java创建链表。
ps:面试喜欢问链表是因为,链表容易理解但是代码难写。
下面java代码就用递归实现一个链表:
如图:
这里写图片描述
首先定义一个Node类,这个很好理解,每个Node包括节点值value,和下一个节点。

public class Node {    private final int value;    private Node next;    public Node(int value){        this.value=value;        this.next=null;    }    public int getValue() {        return value;    }    public Node getNext() {        return next;    }    public void setNext(Node next) {        this.next = next;    }    public static void printLinkedList(Node head){        while (head!=null){            System.out.print(head.getValue());            System.out.print(" ");            head=head.getNext();        }        System.out.println();    }}

下面使用递归思想实现链表创建:

public class LinkedListCreator {    //1.严格定义递归函数作用,包括参数,返回值,side-effect    /**     * 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 static Node createLinkedList(List<Integer> data){        //2.先一般,后特殊。递归退出条件        if (data.isEmpty()){            return null;        }        Node firstNode = new Node(data.get(0));        //每次调用必须缩小规模,每次问题规模缩小程度为1        Node headOfSubList = createLinkedList(data.subList(1,data.size()));        firstNode.setNext(headOfSubList);        return firstNode;    }}

链表的创建比较简单,使用递归几行代码就搞定了。下面看下链表反转的例子。

例子2:链表反转,将上述链表反转

描述:将一个链表反转。
实现如图:
这里写图片描述

主要思想也是使用递归,不断缩小反转的链表长度,最后把第一个元素指向null,并把每次递归的第一个元素的next节点指向第一个节点就ok了public class LinkedListReverse {    /**     * Reverses a linked list.     * @param head the linked list to reverse     * @return head of the reversed linked list     */    public Node reverseLinkedList(Node head){        //size=0 or size=1 递归退出条件        if (head==null||head.getNext()==null){            return head;        }        //递归调用,每次都缩小规模        Node newHead = reverseLinkedList(head.getNext());        //把每次递归的第一个元素的next节点指向第一个节点        head.getNext().setNext(head);        //第一个元素指向null        head.setNext(null);        return newHead;    }}

递归反转使用递归也很容易理解啊!之前想破脑袋写了一大堆的代码还错了,使用递归确实代码异常简介,而且一目了然。

例子3:列出给定数列的所有组合

描述:给定一个任意长度的数列,求出该数列使用n个元素能组成的所有子集。
如图:第一个参数为数列的元素集合,第二个n为子集的长度。[1,2,3,4]中使用2个元素能组成的所有可能:
这里写图片描述

使用递归的实现思路如下:

combinations([1,2,3,4],2)
选1(即数组第0个元素)–>调用combinations([2,3,4],1)
不选1—>调用combinations([2,3,4],2)

代码实现:

/**     * /**     * Generates all combinations and output them,     * selecting n elements from data.     * @param data     * @param n     */    public static 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.println(" ");            }            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);    }}

使用递归的好处想必很明显了,代码简洁明了,方便理解,也方便编码,缺点就是stack的压力太大,效率很低,时间空间开销都很大。下一节使用循环解决这个问题~

以上递归算法小伙伴们可以自己调试,方便大家理解递归的运行过程