zoukankan      html  css  js  c++  java
  • 分治法与递归编程步骤

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

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

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

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

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

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

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

    (2)考虑如何分解原问题。对于某些复杂的问题,会考虑设计一个函数来解决,此时也要设计好该函数的原型,入参、出参和返回值等。

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

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

    (5)处理基本情况。

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

    例子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); //调用写好的函数来合并数组的前后两部分
        }  
    }
  • 相关阅读:
    33.数组声明方式(var构造函数) 、检测数组类型、数组的属性(封装好的就一个length)、数组的方法
    31.this指向(写出调用链,找最近对象) this的默认绑定 隐式绑定 显示绑定(call(绑定对象) apply(绑定对象) 当括号内没放绑定对象的时候恢复默认绑定) bind
    31.
    30.函数作用域链 (GO AO 也叫词法作用域链)、 调用栈、调用栈涉及this绑定
    29.包装类(构造函数) 包装类作用及调用栈
    916. Word Subsets
    246. Strobogrammatic Number
    445. Add Two Numbers II
    2. Add Two Numbers
    341. Flatten Nested List Iterator
  • 原文地址:https://www.cnblogs.com/tsiangleo/p/5263676.html
Copyright © 2011-2022 走看看