vlambda博客
学习文章列表

递归-之二分查找与选择排序算法


中提到Spring aop的拦截器调用链采用递归的方式实现;

之所以本篇单独将递归单独提出来讨论,是觉得递归是一种重要的编程思想;不只是设计原则或者设计模式那种方法论,递归是一种解决问题的思想,其核心是拆解分而治之,将一个大而复杂的问题分解多个小问题,如果分解出来的某些小问题还是无所适从,那么继续分解,直到分解到可以实现为止;

不断分解的过程就是递归思想;递归的思想就是细化问题,直到复杂的问题分解多个简单的小问题;

递归的外观:自己调用自己直到达到某个条件为止,然后依次退出每个方法。
看起来像方法栈的调用,每调用一个方法即压入一个栈帧,直到最后某个方法完成,然后就弹出返回到上一个栈帧,直到全部弹出。

Spring aop的拦截器调用链执行时就具有递归外观;不断调用invocation.proceed();直到拦截器处理完找到最终的invocation执行后依次返回退出;

快速排序;

如何使用递归思想对数组排序呢?

for (int i = 0; i < arr1.length; i++) {
    for (int j = i; j < arr1.length; j++) {
        if (arr1[i] > arr1[j]){
            int temp = arr1[i];
            arr1[i] = arr1[j];
            arr1[j] = temp;
        }
    }
}

在没有递归思想时,大家都习惯于使用更直观的方式来处理排序问题;例如使用上面这个冒泡排序;

冒泡排序非常直观:循环数组,将当前值与后面的值逐个比较,每当后面的值比当前值小时,那么“冒泡”;没经过一轮循环当前被循环的值一定后续值中最小的;当全部循环完成,那么就完成了整个数组的排序;

怎么使用递归思想呢?

两个问题:要分解什么?分解到什么程度为止?

要分解什么?即问题对象是什么?数组排序?数组?排序?

问题的对象是数组,对数组排序;

数组分解到什么程度为止?在数组长度大于一个时,才存在排序一说;如果数组中只有一个数,或者数组本来就是空的那就不用排序了,也可以说这种情况的数组是已经排序好的!

所以对于一个长数组,如果使用递归,不断将其切短,直到每个数组只有一个或者一个都没有时,那么整个数组不就自动排好了吗。

private static void quick_sort(int s[], int l, int r)
{
    if (l < r)
    {
        int i = l, j = r,x = s[l];
        while (i < j)
        {
            while(i < j && s[j] >= x) {
                // 从右向左找第一个小于x的数
                j--;
            }
            s[i] = s[j];
            while(i < j && s[i] < x) {
                // 从左向右找第一个大于等于x的数
                i++;
            }
            s[j] = s[i];
        }
        // x 作为比较基准,将其取出缓存之后,相当于给数据交换数据挪出了空位;
        // 之后每一次交换,都使得主动交换发起者位置的数据沦为过时数据,依旧作为下一次的交换空位;
        // 流程:留出空位,从右向左找第一个小于x的数,与空位交换,使得当前位置空位,正好可以开始从左向右找第一个大于等于x的数交给这个空位;然后循环此过程;
        s[i] = x;
        // 递归调用
        quick_sort(s, l, i - 1); 
        quick_sort(s, i + 1, r);
    }
}

在快速排序的时候,每一次都只是将数组分解为三个数组而已,这三个数组是相对有序的,但是每个小数组是不是有序的不知道;

三个数组分别为(s, l, i - 1),(s, i, i),(s, i + 1, r);

只要每次拆解出来的三个数组是相对有序,那么如果继续拆解每个小的数组直到无法拆解为止(长度小于2),那么整个数组就是有序的了!

二分查找:

二分查找的前提是数组有序。

普通的查找方法是循环遍历(时间复杂度为o(n)):

private static int get(int[] arr1, int t) {
    for (int i = 0; i < arr1.length; i++) {
        if (t == arr1[i]){
            return i;
        }
    }
    return -1;
}

既然是排序好的数据,采用递归思想;

依旧可以像快速排序一样将数据一分为三:(s, l, i - 1),(s, i, i),(s, i + 1, r);

每次都和s(i)比较,如果不是那么只需要从其他两个数组中的一个里面查找即可了,找到这个数组又一分为三循环上述过程,直到数组无法拆分为止;每一次查找都少了一半要标记计算的数据,时间复杂度o(nlogn)

// Like public version, but without range checks.
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                 int key)
 
{
    int low = fromIndex;
    int high = toIndex - 1;

    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = a[mid];// 中间值

        if (midVal < key)
            low = mid + 1;// 在右边继续寻找   如果使用递归调用:binarySearch0(a, mid+1, a.length, key); 
        else if (midVal > key)
            high = mid - 1;// 在右边继续寻找
        else
            return mid; // key found 找到
    }
    return -(low + 1);  // key not found. 找不到
}

jdk中的二分查找使用的是递归思想,并没有递归调用;这样效率更高一点,少了多层递归调用进栈出栈的过程;