递归在实际Android开发项目中的运用

版权声明:本文为博主原创文章,欢迎转载,转载请注明出处。 https://blog.csdn.net/jun5753/article/details/88669299

运用场景

在汽车服务类的 App 应用开发中,如在获取4S店保养方案时,有些保养项目之间存在联动绑定关系。如在选择A项目时,B项目必须同时选择。取消A项目时,B项目可能取消,也可能不取消。绑定关系通过服务器返回的一个数组关系来确定(服务器数据示例见下文)。

举例说明,以某款4S店养车App的使用场景来说,在进入智能方案页时会显示推荐项目(推荐项目会默认选中)

如4S店保养项目、4S店清洗养护项目、4S店维修项目。推荐的项目会默认选中。用户在选择项目时 会关联相应的项目。

(某车邦)实现效果:(请原谅图片大小限制在5M之内,有点模糊,重在达意哈)
在这里插入图片描述

我的实现效果:

在这里插入图片描述

  1. 当用户 取消“机油”选项时,会同时自动取消“机油滤清器”选项。
  2. 当用户选择“机油”选项时,“机油滤清器”选项会同时选中。
  3. 当用户选择养护项目中的”发动机润滑系统养护“选项时,会提示该选项必须与机油、机油滤清器保养项目同时进行。
  4. 当用户取消”发动机润滑系统养护“选项时,只取消当前项目,已选择的”机油“、”机油滤清器“选项不取消。

在以上场景的实现中,在解决联动关系时,用到了递归思想。本文重点记录解决联动的解决思路。

让我们首先回顾一下

什么是递归

标准解答:

所谓递归,简单点来说,就是一个函数直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。

通俗解答:

我们可以把” 递归 “比喻成 “查字典 “,当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词。可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。(摘自知乎的一个回答)

其他解答:

所谓递归,就是包含递推和回归两个自然过程,一方面要由外到里地深入,一方面又要由里到外地回到原点,这是我的基本看法,递推过程是决定整个算法的逐步计算过程,而回归就是将每一步计算的结果慢慢地进行总结,最后就能够得到我们想要的结果。

我们以阶乘为最简单的递归举例:求n! = n * (n-1) * (n-2) * …* 1(n>0) (Kotlin版)

fun Factorial(n: Int): Int {
    if (n == 0) { //if(n<1)
        return 1
    }
    return n * Factorial(n - 1)
}

递归与栈的关系

常常听到 “递归的过程就是出入栈的过程”,这句话怎么理解?我们以上述代码为例,取 n=3,则过程如下:

在这里插入图片描述

  • 第 1~4 步,都是入栈过程,Factorial(3)调用了Factorial(2)Factorial(2)又接着调用Factorial(1),直到Factorial(0)

  • 第 5 步,因 0 是递归结束条件,故不再入栈,此时栈高度为 4,即为我们平时所说的递归深度;

  • 第 6~9 步,Factorial(0)做完,出栈,而Factorial(0)做完意味着Factorial(1)也做完,同样进行出栈,重复下去,直到所有的都出栈完毕,递归结束。

**每一个递归程序都可以把它改写为非递归版本。**我们只需利用栈,通过入栈和出栈两个操作就可以模拟递归的过程,二叉树的遍历无疑是这方面的代表。

但是并不是每个递归程序都是那么容易被改写为非递归的。某些递归程序比较复杂,其入栈和出栈非常繁琐,给编码带来了很大难度,而且易读性极差,所以条件允许的情况下,推荐使用递归。

什么时候该用递归

当我们遇到一个问题时,我们是怎么判断该题用递归来解决的?

问题可用递归来解决需具备的条件:

  1. 子问题需与原问题为同样的事,且规模更小;

  2. 程序停止条件。

递归的元素总共有三类:

  1. 初始值
  2. 结束条件
  3. 算法

在实际项目中运用

实例代码(Kotlin):

 //关联的绑定 Ids数组
 private var bindItemIds = ArrayList<Int>()
    
 private fun getBindItemIds(
        listBindItems: ArrayList<MaintenancePlanEntity.BindItemsBean>,
        selectedItemId: Int,
        isSelected: Boolean
    ): ArrayList<Int> {
        for (index in 0..listBindItems.size) {
            if (index == listBindItems.size) {
                //返回空数组
                return bindItemIds
            }
            val item = listBindItems[index]
            //选中
            if (isSelected) {
                if (selectedItemId == item.firstItemId && !bindItemIds.contains(item.bindItemId)) {
                    bindItemIds.add(item.bindItemId)
                    return getBindItemIds(listBindItems, item.bindItemId, isSelected)
                }
          //取消选中
            } else {
                if (selectedItemId == item.bindItemId && !bindItemIds.contains(item.firstItemId)) {
                    bindItemIds.add(item.firstItemId)
                    return getBindItemIds(listBindItems, selectedItemId, isSelected)
                }
            }
        }
        return bindItemIds
    }

服务器返回绑定关系数据示例:

"bind_items": [
      {
          //选择的项目id
        "first_id": 2010,
          //选择first_id后必须选择的id ,
        "bind_id": 7
      },
      {
        "first_id": 2011,
        "bind_id": 15
      },
      {
        "first_id": 1,
        "bind_id": 2
      },
      {
        "first_id": 2001,
        "bind_id": 2
      },
      {
        "first_id": 2,
        "bind_id": 1
      }
    ]

方法调用:实例代码(Kotlin)

……(省略部分代码)
//在选择与取消时:
val item = list[position]
item.isSelected = !item.isSelected
                //获取当前itemId
                val selectedItemId = item.itemId

                bindItemIds.clear()
                //得到关联的Ids 数组
                val bindItemIds = getBindItemIds(listBindItems, selectedItemId, item.t.isSelected)

                for (bindItemId in bindItemIds) {
                    for (index in list.indices) {
                        val bean = list[index]
                        bean?.let {
                            if (it.itemId == bindItemId) {
                                it.isSelected = item.isSelected
                            }
                        }
                    }
                }
				//界面刷新
                adapter.notifyDataSetChanged()
                //计算关联后的价格
                calculateTotalPrice()
……(省略部分代码)

递归的经典使用场景

  1. 斐波拉契数列

    斐波那契数列指的是这样一个数列:0,1,1,2,3,5,8,13,21……

    数学公式:

    F(n) = F(n-1) + F(n-2)

    代码示例:(Kotlin实现)

    fun fib(n: Int): Int {
        if (n <= 2) return 1
        return fib(n - 1) + fib(n - 2)
    }
    
  2. 遍历文件(Kotlin实现)

fun list(fPath: File){
    val files = fPath.listFiles()
    for (f in files){
       println(f.path)
        if (f.isDirectory){
            list(f)
        }
    }
}

3.快速排序

快速排序法采用的是“分而治之”的思想,比较适合使用递归而且效率也相当不错。

实例代码(Kotlin):

fun quickSort(arr: IntArray, left: Int, right: Int) {
        if (left < right) {
            val mark = arr[left]
            var i = left
            var j = right
            while (i < j) {
                // 从后向前查找
                while (j > i && arr[j] >= mark) {
                    j--
                }
                if (j > i) {
                    arr[i++] = arr[j]
                }
                // 从前向后查找
                while (i < j && arr[i] < mark) {
                    i++
                }
                if (i < j) {
                    arr[j--] = arr[i]
                }
            }
            arr[i] = mark
            quickSort(arr, left, i - 1)
            quickSort(arr, i + 1, right)
        }
    }

总结

在开发中,刚开始可能不知是否需要用递归来实现,写着时发现当函数要不停的调用自己的时候,即你接下来的代码步骤跟你之前的是一致的时候。你就需要考虑用递归来实现了,实现时一定要考虑循环退出的终止条件。考虑好终止条件通用情况

参考资料:

1.一文读懂递归算法

2.知乎:如何理解汉诺塔的递归?

3.Binary, Hanoi and Sierpinski, part 1(youtobe视频讲解,需翻墙)

猜你喜欢

转载自blog.csdn.net/jun5753/article/details/88669299