分治法与递归编程步骤

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分治法与递归编程步骤相关的知识,希望对你有一定的参考价值。

分治法是一种很强大的算法设计方法。基本思想是:将原问题分解为几个规模小但类似于原问题的子问题,递归的求解这些子问题,然后再合并这些子问题的解来建立原问题的解。

在分治策略中,递归地求解一个问题,在每层递归中应用如下三个步骤:

(1)分解(Divide):将原问题分解为一些子问题,子问题的形式与原问题一样,只是规模更小。

(2)解决(Conquer):递归地解出子问题。如果子问题规模足够小,则停止递归,直接求解。

(3)合并(Combine):将子问题的解组合成原问题的解。

 

分治思想体现在编码上,往往就是递归的形式。实际在编码的时候,可以遵循如下步骤:

(1)精心设计函数原型,包括入参、出参和返回值等。

(2)调用(1)中的函数处理各个子问题,并假设子问题已经解决了。

(3)处理子问题合并的具体细节。

(4)处理基本情况。

(5)将(4)中的代码移到函数体的前面,整理代码结构。

 

例子1:递归版插入排序 

为了排序A[1..n],我们递归地排序A[1..n-1],然后把A[n]插入已排序的数组A[1..n-1]。

第一步:设计函数原型

void my_insertion_sort(int a[], int left, int right) //将数组a[left...right]之间的元素排序,left和right都是下标,从0开始取值.
{
}

第二步:调用函数解决子问题

void my_insertion_sort(int a[], int left, int right) //将数组a[left, ...right]之间的元素排序,left, 和right都是下标,从0开始取值。
{
    my_insertion_sort(a, left, right - 1); //调用函数给数组a[left .. right-1]之间的元素排序
    
    //数组a[left .. right-1]已经排好序了,接下来就是将a[right]插入到a[left .. right-1]中的适当位置了。
}

第三步:合并子问题

接下来要编写的代码就是将a[right]插入到a[left .. right-1]中的适当位置,如下:

void my_insertion_sort(int a[], int left, int right) //将数组a[left, ...right]之间的元素排序,left, 和right都是下标,从0开始取值。
{
    my_insertion_sort(a, left, right - 1); //调用函数给数组a[left .. right-1]之间的元素排序
    
    //将a[right]插入到a[left .. right-1]中
    int j = right - 1;
    int temp = a[right];
    while (j >= left && temp < a[j])
    {
        a[j + 1] = a[j];
        --j;
    }
    a[j + 1] = temp;
    //至此,a[right]已经插入到a[left .. right-1]中的适当位置了
}

第四步:处理基本情况

查看函数原型void my_insertion_sort(int a[],int left,in right); 发现当left等于right的时候,待排序区间就一个元素,直接返回。

void my_insertion_sort(int a[], int left, int right) //将数组a[left, ...right]之间的元素排序,left, 和right都是下标,从0开始取值。
{
    my_insertion_sort(a, left, right - 1); //调用函数给数组a[left .. right-1]之间的元素排序//将a[right]插入到a[left .. right-1]中
    int j = right - 1;
    int temp = a[right];
    while (j >= left && temp < a[j])
    {
        a[j + 1] = a[j];
        --j;
    }
    a[j + 1] = temp;
    //至此,a[right]已经插入到a[left .. right-1]中的适当位置了

    //当left == right的时候就是基本情况,此时就直接返回了。
    if(left == right)
       return;
}

第五步:优化代码结构,将第四步的代码移到前面。

void my_insertion_sort(int a[], int left, int right) //将数组a[left, ...right]之间的元素排序,left, 和right都是下标,从0开始取值。
{
    //当left == right的时候就是基本情况,此时就直接返回了。
    if(left == right)
        return;

    my_insertion_sort(a, left, right - 1); //调用函数给数组a[left .. right-1]之间的元素排序//将a[right]插入到a[left .. right-1]中
    int j = right - 1;
    int temp = a[right];
    while (j >= left && temp < a[j])
    {
        a[j + 1] = a[j];
        --j;
    }
    a[j + 1] = temp;
    //至此,a[right]已经插入到a[left .. right-1]中的适当位置了
}

或者最好是

void my_insertion_sort(int a[], int left, int right) //将数组a[left, ...right]之间的元素排序,left, 和right都是下标,从0开始取值。
{
    if(left < right)    //处理边界条件
    {
        my_insertion_sort(a, left, right - 1); //调用函数给数组a[left .. right-1]之间的元素排序//将a[right]插入到a[left .. right-1]中
        int j = right - 1;
        int temp = a[right];
        while (j >= left && temp < a[j])
        {
            a[j + 1] = a[j];
            --j;
        }
        a[j + 1] = temp;
        //至此,a[right]已经插入到a[left .. right-1]中的适当位置了
    }
    
}

 

例子2:归并排序

归并排序就是分治思想的典型例子。

第一步:设计函数原型:

比如要将数组a[left...right]之间的元素排序,可以设计如下原型:

void my_merge_sort(int a[],int left,in right); // left和right分别是待排序区间的左右下标,取值从0开始。
{

}

第二步:调用函数解决子问题:

我们将待排序区间分成两部分,并在这两部分上调用我们的函数解决它。

void my_merge_sort(int a[],int left,in right); // left和right分别是待排序区间的左右下标,取值从0开始。
{
    int mid = ( left + right ) / 2;    //将待排序数组均分为两部分,递归解决
    my_merge_sort(a,left,mid);        //给数组的前半部分排序
    my_merge_sort(a,mid+1,right);    //给数组的后半部分排序

    //数组的前、后半部分都已经排好序了,接下来就是合并了。
}

第三步:合并子问题

设计一个函数merge(int a[],int l,int m,int r)来处理两个已排序数组的合并问题,这里不给出实现。

void my_merge_sort(int a[],int left,in right); // left和right分别是待排序区间的左右下标,取值从0开始。
{
    int mid = ( left + right ) / 2;    //将待排序数组均分为两部分,递归解决
    my_merge_sort(a,left,mid);      //给数组的前半部分排序
    my_merge_sort(a,mid+1,right); //给数组的后半部分排序
merge(a,left,mid,right); //调用写好的函数来合并数组的前后两部分 }

第四步:处理基本情况

对于之前设计的排序函数的原型:void my_merge_sort(int a[],int left,in right); 当left等于right的时候,待排序区间就一个元素,直接返回。

void my_merge_sort(int a[],int left,in right); // left和right分别是待排序区间的左右下标,取值从0开始。
{
     int mid = ( left + right ) / 2;    //将待排序数组均分为两部分,递归解决
    my_merge_sort(a,left,mid);      //给数组的前半部分排序
    my_merge_sort(a,mid+1,right); //给数组的后半部分排序

    merge(a,left,mid,right); //调用写好的函数来合并数组的前后两部分

    //当left == right的时候就是基本情况,此时就直接返回了。
    if(left == right)
        return;
}

第五步:优化代码结构,将第四步的代码移到前面。此时的代码结构如下:

void my_merge_sort(int a[],int left,in right); // left和right分别是待排序区间的左右下标,取值从0开始。
{
    //当left == right的时候就是基本情况,此时就直接返回了。
    if(left == right)
        return;

    int mid = ( left + right ) / 2;    //将待排序数组均分为两部分,递归解决
    my_merge_sort(a,left,mid);      //给数组的前半部分排序
    my_merge_sort(a,mid+1,right); //给数组的后半部分排序

    merge(a,left,mid,right); //调用写好的函数来合并数组的前后两部分
  
}

 或者最好是

void my_merge_sort(int a[],int left,in right); // left和right分别是待排序区间的左右下标,取值从0开始。
{
    if(left < right)
    {
        int mid = ( left + right ) / 2;    //将待排序数组均分为两部分,递归解决
        my_merge_sort(a,left,mid);      //给数组的前半部分排序
        my_merge_sort(a,mid+1,right); //给数组的后半部分排序

        merge(a,left,mid,right); //调用写好的函数来合并数组的前后两部分
    }  
}

 

以上是关于分治法与递归编程步骤的主要内容,如果未能解决你的问题,请参考以下文章

递归的逻辑——递归与分治

动态规划问题

递归思想

如何快速正确的写出各种分治算法的实现代码

第二章总结和结对编程情况

分治法解决最大子数组问题