• 递归的理解---1


    开门见山:

    不要试图在脑中复现递归函数的调用栈,你只要相信递归函数的作用即可。

    重点提示:

    1.不要试图在脑中复现递归函数的调用栈,你只要相信递归函数的作用即可。重要的事情多说一遍,否则你容易陷入一种脑循环(你的小脑袋瓜嗡嗡的也循环不出来),如果是单层的递归调用你可能还好理解,但是如果是函数多次调用自己进行递归,你会发现跟着开发工具步步跟调,也很难去理解中间这个调用过程,且调用次数稍多,就涉及到大量的步骤,我们只要相信算法本身,相信上一步会给我们返回准确的结果,相信它的作用即可。
    2.一定要理解递归的真切含义,是先递过去再从后面依次送过来,就像别的文章中经常举的一个例子,一个长长的队伍,你在队伍中间,想知道你是第几个,你拍前一个人的肩膀,前面的人也不知道,继续问前面的,直到问道第一个人,第一个人扭头告诉第二个人,然后从第二个人依次向后位置依次加1,直到最后再走回你这里,你知道了你的位置。

    具体讲解:

    第一、 明确递归函数的作用和参数的含义,然后坚定的相信它的作用


    再次强调不要试图在脑中重演递归过程,当然你也做不到。你要做的便是:明确递归函数的作用,包括参数是什么,是否有返回值,有的话返回值是什么,然后相信它就行了。

    第二、找到递归基


    所谓递归基,就是递归结束的条件。比如 n == 0的时候之类的情况。

    第三、明确递归函数返回后,该做点啥


    里层的递归函数返回后,需要与当前的层做一些互动,然后才能将彼此联系起来。

    思考技巧
    就是将问题拆分成两个部分, 即1剩余整体,其中剩余整体又可以用1剩余整体的思想来考虑.如此思考,那么剩余整体完全可以用递归的方法去解决.
    重中之重的点如下联系到第一点技巧的提示,即_相信剩余整体已经处理完毕_之后如何与_1的部分_衔接问题.

    现在这么一说可能会很抽象,在例子中会给出很形象的解释.


    示例分析

    求和

    使用for循环很容易求和1+2+3+...+n, 那么使用递归的方法呢?

    • 定义求和函数sum.明确函数作用, 给定一个n,sum(n)返回1+2+...+n的总和
    • 找到递归基, 当n=1时,返回1
    • 利用1剩余整体的思想找到递推式.1+2+...+n可以分解成1+2+...+n-1n的和,其中最后的n即可代表1的思想,1+2+...+n-1代表剩余整体的思想.相信sum函数的作用,即sum(n-1)返回1+2+...+n-1的结果.总结一下即为nsum(n-1)的问题.

    代码如下

        public static int sum(int n){
            if (n == 1){
                return 1;
            }
            return n + sum(n - 1);
        }
        sum(10)
        # 55
    

    当我们写 return n + sum(n-1) 要想到关于递归的第一个技巧,我们假设sum(n-1)是没有问题的,正常返回1+2+...+n-1的和,不要自己绕进去.递归求和就是这么简单.

    数组内部的最大值


    用循环的方法,同样是很简单的.那么递归呢

    假设有数组

    l = [23, 34, 2, 4, 56, 23, 1, 56, 78]
    
    • 定义一个求数组最大值的函数max.明确其作用,给定一个数组,返回其中的最大值
    • 找到递归基,即当数组的元素为1时,返回这个数组元素即可
    • 利用1整体思想去寻找递归关系式.将数组第一项的23作为1, 将剩下的[34, 2, 4, 56, 23, 1, 56, 78]作为一个整体.则 比较 23max([34, 2, 4, 56, 23, 1, 56, 78])两者的大小,如果前者大,返回23,否则返回后者.

    代码如下

    public class Recursion {
        public static void main(String[] args) {
            int[] arr = {1, 5, 3, 9, 13, 7, 25, 19, 37, 123, 16, 255, 1234, 125, 1379, 233, 345, 235, 7890, 11111};
            int maxNumber = MaxNumber(arr, 0, arr.length - 1);
            System.out.println("maxNumber = " + maxNumber);
        }
    
        public static int MaxNumber(int[] arr, int low, int high) {
            //随着递归的进行,数组的边界会改变.
            //直接定义在接口处,递归更容易实现和理解.因为这样一来,容易切割实现1和剩余整体的思想.
            if (low == high) {
                //递归基,此时数组中只有一个元素,返回即可
                return arr[low];
            }
            int left = arr[low];//1的思想
            int right = MaxNumber(arr, low + 1, high);//剩余整体的思想
            return Math.max(left, right);
        }
    }
    

    单向链表的翻转

    牛客网题

    输入一个链表,反转链表后,输出新链表的表头

    假设有一个链表

    1->2->3->4->5->NULL
    
    • 定义一个函数ReverseList, 翻转一个链表.返回新链表的头结点
    • 找到递归基,如果一个链表只有一个节点,那么直接返回即可.
    • 利用1和剩余整体的思想去解决此问题.

    我们将原始链表的头结点1作为1,将2->3->4->5->NULL作为剩余整体.即第一个节点与ReverseList(2->3->4->5->NULL)如何链接的问题.

    我们相信ReverseList函数作用,即ReverseList(2->3->4->5->NULL)已经被翻转成功,而且返回新链表的头结点.

    我们如何连接这两个部分呢?

    代码如下

    /*
    struct ListNode {
    	int val;
    	struct ListNode *next;
    	ListNode(int x) :
    			val(x), next(NULL) {
    	}
    };*/
    
    // 递归算法
    class Solution {
    public:
        ListNode* ReverseList(ListNode* head) {
            // 递归基
            if (head == NULL || head->next == NULL) return head;
            // 当前节点的下一节点
            ListNode* next_node = head->next;
            ListNode* new_head = ReverseList(next_node);
            next_node->next = head;  # 下面这两句是建立两部分的连接
            head->next = NULL;       # 当前节点的下一节点需要保存一下
            return new_head;
        }
    };
    
    
    // 迭代算法
    class Solution {
    public:
        ListNode* ReverseList(ListNode* pHead) {
            if (!pHead || !pHead->next) {
                return pHead;
            }
            ListNode* pPre = NULL;
            ListNode* pNode = pHead;
            ListNode* pNext = NULL;
            while (pNode) {
                pNext = pNode->next;
                pNode->next = pPre;
                pPre = pNode;
                pNode = pNext;
            }
            return pPre;
        }
    };
    

    二叉树的最大深度

    nowcoder

    输入一棵二叉树,求该树的深度.从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度.

    class Solution {
    public:
        // step1: 定义TreeDepth函数,它的作用是找到并且返回给定树节点对应的树的最大深度
        int TreeDepth(TreeNode* pRoot)
        {
            // 找到递归基
            if (pRoot == NULL) return 0;
            // 递归关系式 Depth(root) Depth(root->left) Depth(root->right)
            int left = TreeDepth(pRoot->left);
            int right = TreeDepth(pRoot->right);
            // 根据递归关系式与返回值,决定最终返回 1+max(left, right)
            return 1 + max(left, right);
        }
    };
    

    例子未完待续...

  • 相关阅读:
    c# 网络编程
    .net基础------抽象类和接口区别
    自己开发插件-------- 待续...........
    js 学习笔记 (this ,扩展方法,匿名函数)
    meta
    微信公众号支付接口-JSAPI
    跨境电商-311xml报文生成 更新到2018-10
    MooTools 异步请求验证
    微信JS-SDK 接口调用与 php 遇到的坑
    php 与 jquery中$.post()与attr()方法的简单实例 amaze modal 模态窗口
  • 原文地址:https://www.cnblogs.com/lovelywcc/p/14200281.html
Copyright © 2020-2023  润新知