如何通过冒泡学递归

冒泡

前言

来自我的好朋友 EvilSay 的投稿,以下是原文:

冒泡排序,相信大家听到这四个字都觉得很简单,我觉得也是,但能不能更简单呢?比如,用递归实现。

普通冒泡

public static int[] bubblerecursion (int array[]) {
    for (int i = 0;i < array.length - 1; i ++) {
        for (int j = i + 1;j < array.length;j ++) {
            if (array[j] < array[i]) {
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
    }
    return array;
}

普通的冒泡排序实现方式如上面代码所示,两个 for 循环 + 一个 if 判断。且不说代码量的问题,但就这可读性就把人给烦死。下面看看清新脱俗的递归冒泡是如何实现的。

递归冒泡

public int[] bubblerecursion(int[] nums){

  // step 1
  for (int i = 0; i < nums.length-1; i++) { 
    // step 2
    if(nums[i + 1] < nums[i] ){

      int tmp = nums[i+ 1];

      nums[i+ 1] = nums[i];

      nums[i] = tmp;
      // step 3
      return bubblerecursion(nums);
    }
  }
  // step 4
  return nums;
}
整段代码分为 4 部分,放入 Int 数组 nums = {1, 6, 2}
step 1

第一步为递归边界,当它完成循环后要么先上一层循环返回值,要么终止整个自身调用并返回最终处理的值,当代码运行到第一步:i=0,i 要小于数组长度减 1,i++

step 2

把数组中第 i 位与第 i+1 位进行判断,如果为 true 则进行数组位置调换并进行到第三步,如果为 false 则进入下一次 for 循环并在 for 循环结束后进入第四步。

注意:如果 step2 条件为 true 的话则不需要进入 step 4。

step 3

这是最绕的一步,代码调用自身形成递归。通过参数我们可以得知,如果代码执行到 step 3 那现在放入的参数变为 nums[1,2,6]。并在 step3 的时候开启一个新的循环。

注意:此时代码等于停在了 step3 并等待新的循环传来的值,接收到最终循环传来的值终止自身调用并把结果返回给调用方。

实战:解决 LeetCode 的 203 号问题:使用递归删除链表中等于给定值 val 的所有节点。

/** 第203号问题链表的实现
 * @Author: EvilSay
 * @Date: 2019/7/31 23:15
 */
public class ListNote {
    public int val;
    public ListNote next;

    public ListNote(int x){
        val = x;
    }
    //链表节点的构造函数
    //使用arr为参数,创建一个链表,当前ListNote为链表头节点
    public ListNote(int[] arr){
        if (arr == null || arr.length == 0)
            throw new IllegalArgumentException("arr can not be empty");

        this.val = arr[0];
        //通过访问构造方法获取cur的第0个参数位置
        ListNote cur = this;
        for (int i = 1; i <arr.length ; i++) {
            //为下一个节点创建新的空间
            cur.next = new ListNote(arr[i]);
            //为下一位节点赋值
            cur = cur.next;
        }
    }
    //以当前节点为头节点的链表信息字符串
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        ListNote cur = this;
        while (cur != null){
            res.append(cur.val + "->");
            cur = cur.next;
        }
        res.append("NULL");
        return res.toString();
    }
}
//普通解法
    public ListNode removeElements(ListNode head,int val){
        
        while (head != null && head.val == val){
            
            head = head.next;
        }

        if (head == null)
            return null;
        ListNode prev = head;
        //检查下一个节点是否需要删除
        while (prev.next != null){
            //prev下一个节点如果等于要删除的节点
            if (prev.next.val == val)
                //则下一个节点等于下下个节点
                prev.next = prev.next.next;
            else
                prev = prev.next;
        }
        return head;
    }
//递归解法
    public ListNote removeElements(ListNote head,int val){
        if (head == null)
            return null;
       head.next = removeElements(head.next,val);

        return head.val == val ? head.next : head;
    }

请务必按照数字顺序阅读代码

模拟调用6->7->null删除元素7

1:代码运行到第一步:获取的节点为空时(当前节点为6)返回Null给上一层递归,如果有的。

2:代码运行到第二步:(当前节点为6)当前节点的下一位等于heade.next == 7并把节点传入递归中

3:进入递归调用,并重新回到代码运行第一步(当前节点为6)

4:代码运行到第一步:获取的节点为空时(当前节点为7)返回Null给上一层递归。

5:代码运行到第二步:(当前节点为7)当前节点的下一位等于heade.next == null并把节点传入递归中

6:进入递归调用,并重新回到代码运行第一步(当前节点为null)

7:代码运行到第一步:获取的节点为空时(当前节点为null)返回Null给上一层递归。

8:代码运行到第三步:判断当前节点是否等于要删除的节点如果等于则返回上一层递归传来的节点,如果不等于则返回当前节点,(返回上一层递归传来的节点)

9:代码运行到第三步:判断当前节点是否等于要删除的节点如果等于则返回上一层递归传来的节点,如果不等于则返回当前节点,(返回当前节点)

后语

对比了二者,我认为递归冒泡在代码简洁性、可读性方面有优势。对于没有算法基础的朋友来说,刚接触到递归,可能会觉得有点绕,这是正常的。算法是一门很神奇的学问,它有难度、有意思,学会了往往这样的东西,能让你受益终生。

推荐阅读

java | 什么是动态代理?

SpringBoot | 是如何实现日志的?

SpringBoot | 是如何实现自动配置的?

最后

如果看到这里,喜欢这篇文章的话,请转发、点赞。微信搜索「一个优秀的废人」,欢迎关注。

回复「1024」送你一套完整的 java、python、c++、go、前端、linux、算法、大数据、人工智能、小程序以及英语教程。

回复「电子书」送你 50+ 本 java 电子书。

编程语言

一个优秀的废人

posted @ 2020-01-17 15:44  JavaFish  阅读(368)  评论(0编辑  收藏  举报