递归与分治

一、为什么要有递归与分治?

任何可以用计算机求解的问题所需要的计算时间都与其规模有关,问题的规模越小,解题使用的时间就会越短以及越容易处理。

例如:n!(n的阶乘)。对于其来说 :

  当n = 1 时1 = 1;

  当n = 2 时2 * 1=  2;

  当n = 3 时3 * 2 * 1= 6;

  ...

  当n较大时,问题的处理就不再那么容易了。此时我们引进了递归与分治策略。

 

二、什么是递归与分治

2.1、概念:

  递归:直接或间接调用自身的算法成为递归算法。

  分治:将一个难以解决的大问题拆解为若干个类似的小问题,以便于各个击破,分而治之。

  递归与分治:由分治拆解的类似小问题,这些问题的解决思路基本一致,这样使得递归往往可以派出用场。

 

三、递归与分治的使用

3.1. 递归

例1:阶乘函数

阶乘函数可定义为:

        

 

从上述可以看出,对于递归来说,主要有以下两点:

  1> 必须有非递归函数的初始化值,否则递归函数无法计算。

  2> 递归函数用较小自变量的函数值来表示较大自变量的函数值。

 

//n的阶乘
int
factorial(int n) { if(n == 0) { return 1; } return n * factorial(n -1); }

 

3.2 双递归函数

概念:当一个函数及他的一个变量由函数自身定义时,称这个函数就是双递归函数。

例2:Ackerman函数

Ackerman函数可定义为:

        

 

从上述可以看出,对于双递归来说,主要有以下两点:

  1>双递归相比单递归多了一个变量。

  2>双递归也是使用较小的两个自变量来表示较大两个自变量的函数值。

 

 

3.3 分治

分治常见的三种算法:

  1>二分搜索技术

  2>合并排序

  3>快速排序

例3:对于已经排序好的n个元素,现在要在其中找出一个特定的元素key;

//二分搜索技术
int
binarySearch(int[] array, int key) { int left = 0; int right = array.length - 1; while(left <= right) { int middle = (left + right) / 2; if(key == array[middle]) { return middle; } else if(key > array[middle]){ left = middle + 1; } else { right = middle - 1; } } return -1; }

从上述可以看出,主要有以下几点:

  1.取数组中间值middle与key值进行比较。

  2.如果相等,直接返回。

  3.如果key > middle值,middle值下标相对应的左侧都小于key值,无任何意义,此时重置左下标left为middle + 1。

  4.如果key < middle值,middle值下标相对应的右侧都大于key值,无任何意义,此时重置右下标left为middle - 1。

  5.一直重复2,3,4这三个步骤直到得到返回值或者左指针大于右指针。

 

例4:使用分治策略对n个元素进行排序

//合并排序
void
mergeSort(int[] array, int left, int right) { if(left < right) { int middle = (left + right) / 2; //取中间点 mergeSort(array, left, middle); mergeSort(array, middle + 1, right); int[] array2 = merge(array, left, middle, right); copy(); //复制回数组array } } int[] merge(int[] array, int left, int middle, int right) { int[] result = new int[array.length]; //将两个排序好的数组组合成一个新的数组 //... return result; }

从上述可以看出,主要有以下几点:

  1.取数组中间值下标middle。

  2.使得middle左边(包含middle下标)有序。

  3.使得middle右边有序。

  4.合并两个有序数组。

  5.一直重复1,2,3,4三个步骤直到left等于right,代表此时子数组中只有一个数,则无需排序。

 

例5:使用分治对n个数组进行排序  

 //快排
    public void quickSort(int [] a, int low, int high) {
        if(low >= high) {
            return;
        }

        int keyPos = partition(a, low, high);
        quickSort(a, 0, keyPos);
        quickSort(a, keyPos + 1, high);

    }

  //获取基准的下标
private int partition(int[] a, int low, int high) { int key = a[low]; while(low < high) { while(low < high && a[high] > key) high--; a[low] = a[high]; while(low < high && a[low] < key) low++; a[high] = a[low]; } a[low] = key; return low; }

从上述可以看出,主要有以下几点:

  1.选取基准,一般以第一个元素作为基准。

  2. 根据此基准,保存基准为key值,使用low与high两个指针分别从数组的左右进行遍历。

  3.对数组从右遍历找到小于key的数,将其赋值为对应的下标为low的元素。

  4.对数组从左遍历找到大于key的数,将其赋值为对应的下标为high的元素。

  5.重复3,4步骤使得直到low等于high为止, 此时low的下标对应的元素赋值为基准值即可。

  6.此时,保证基准下标左边的元素都小于基准值,基准下标右边的元素都大于基准值。

  7.分别对基准下标左侧(包含基准)与基准下标右侧重复上述1,2,3,4,5,6直到low >= high即可(此时子数组中的元素小于等于1。

 

总结:递归与分治往往都是伴随出现的,对于递归来说一般都会定义初始值,对于分治来说一般通过拆解为多个类似的小问题进行解决,最终的小问题都有一定的边界限制可以作为不可再拆的信号。

 

 

posted @ 2020-03-25 13:34  看不懂的猴子  阅读(1291)  评论(0编辑  收藏  举报