zoukankan      html  css  js  c++  java
  • 《算法导论》读书笔记--第二章 2.3 设计算法

    我们可以使用的算法设计技术有很多。插入排序用的是增量方法,即在已经排好的数组中不断加入新的元素。下面考虑一种被称为“分治法”的设计方法。

    2.3.1分治法

    分治法的思想:将原问题分解为几个规模较小但是类似于原问题的子问题,递归地求解这些子问题,然后合并这些子问题的解来建立原问题的解。分治模式在每层递归时有三个步骤:

    分解原问题为若干子问题;

    解决这些子问题,递归地求解各子问题,若子问题规模足够小,则直接求解;

    合并这些子问题的解成原问题的解。

    归并排序算法完全遵循分治模式,操作如下:

    分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列;

    解决:使用归并排序递归地排序两个子序列;

    合并:合并已经排序的子序列以产生已排序的答案。

    当被排序的序列长度为1时,递归“开始回升”。

    下面写出合并这一步的伪代码,时间复杂度为theta(n).

    //MERGE  伪代码
    n1 = q - p + 1
    n2 = r - q 
    Let L[1..n1+1] and R[1..n2+1] be new arrays
    for i = 1 to n1
        L[i] = A[p + i -1]
    for j = 1 to n2
        R[j] = A[q+j]
    L[n1 + 1] = ∞
    R[n2 + 1] = ∞
    i = 1
    j = 1
    for k = p to r
        if L[i] <= R[j]
            A[k] = L[i]
            i = i + 1
        else A[k] = R[j]
            j = j + 1

    注意点:

    1、左右两个子序列都已经排好序,从左右两个序列中依次取出最小的数存入原数组中;

    2、左右两个子序列分别递增,在哪一个子列选出一个数,这个序列下标加1;

    3、为了不用每次确认子序列是否为空,要在子序列的最后放一张“哨兵牌”,这张牌非常大,当碰到这张牌时,一定是另一个序列的数被选出,当两个子序列都到达“哨兵牌”时,此时恰好所有数都存进原数组了(由循环头的迭代数目控制);在左右序列的长度不一致时,哨兵牌才会发挥威力,两个子序列长度一样的时候,同时达到最后一张牌,那么“哨兵牌”就没什么作用了;

    4、另外注意的是,需要开辟新的数组来存储每个子序列,注意下标的确认,尤其是计算子序列长度时不要弄错。

    下面我们把MERGE作为归并排序的一个子程序,下面是MERGE-SORT的伪代码:

    MERGE-SORT(A,p,r)
    if p < r                  //这里不能有等号,否则死循环
        q = [p+r]/2 (向下取整)
        MERGE-SORT(A,p,q)
        MERGE-SORT(A,q+1,r)
        MERGE(A,p,q,r)

    注意点:

    1、为了排序A[p,...,r],首先要调用MERGE-SORT(A,1,A.length);

    2、然后不断分解A;

    3、在到了基本情况的时候,“向上回滚”,不断将子序列进行合并,知道将n个数全部合并好为止;

    4、十分要注意的是分治算法的分解和合并过程(这里还需要进行详细分析);

    5、实际上,归并算法并“没有”排序的显示过程,算法在不断分解数组,直到基础情况,再进行合并,这个合并的过程中才有“排序”的过程。

    //归并排序c++代码
    #include <iostream>
    #include <time.h>
    
    const int MAX = 1e6;
    
    void MERGESORT(int*, int,int);
    void MERGE(int*,int,int,int);
    
    using namespace std;
    
    int main()
    {
        clock_t start, end;
        start = clock();
    
        int i;
        int* arr = new int[100];
        for (i = 0; i < 100; i++)
        {
            arr[i] = 100 - i;
        }
        
        MERGESORT(arr,0,99);
    
        for (i = 0; i < 100; i++)
        {
            cout << arr[i] << " ";
            if (i % 10 == 9)
            {
                cout << "
    ";
            }
        }
        delete[]arr;
    
    
        cout << "__________________" << endl;
        end = clock();
        cout << "Run time: " << (double)(end - start) / CLOCKS_PER_SEC << "s" << endl;
    
        return 0;
    }    
    
    void MERGESORT(int* a, int p, int r)
    {
        int q;
    
        if (p < r)                        //这里不能有等号,否则会进入死循环的,你猜我怎么知道的?
        {
            q = (p + r) / 2;
            MERGESORT(a, p, q);
            MERGESORT(a, q + 1, r);
            MERGE(a, p, q, r);
        }
    }
    
    void MERGE(int* arr, int p, int q, int r)
    {
        int n1 = q - p + 1;
        int n2 = r - q;
    
        int* Left = new int[n1 + 1];
        int* Right = new int[n2 + 1];
    
        int i, j;
    
        for (i = 0; i < n1; i++)
            Left[i] = arr[p + i];
        for (j = 0; j < n2; j++)
            Right[j] = arr[q + j + 1];
    
        Left[n1] = MAX;
        Right[n2] = MAX;
    
        i = 0;
        j = 0;
    
        for (int k = p; k <= r; k++)
        {
            if (Left[i] <= Right[j])
            {
                arr[k] = Left[i];
                i++;
            }
            else
            {
                arr[k] = Right[j];
                j++;
            }
    
        }
        delete []Left;
        delete []Right;
    }

    2.3.2 分析分治算法

    当一个算法包含对其自身的递归调用时,我们往往可以用递归方程或递归式来描述其运行时间,该方程根据在较小输入上的运行时间来描述在规模为n的问题上的总运行时间。

    分治算法运行时间的递归式来自基本模式的三个步骤。若问题规模足够小,比如对某个常量c,n<=c,则直接求解需要常量时间,记为theta(1)。假设原问题分解为a个子问题,每个子问题的规模是原问题的1/b(注意:这里的a和b不一定相等)。若分解子问题需要时间为D(n),合并子问题的解成原问题的解需要时间C(n),则得到递归式:

    image

    归并排序算法的分析

    假定n是2的幂,为了简便可以将n设为2的次幂,我们将看到这样的假设不影响递归式解的增长量级。假定归并一个元素需要常量时间。当n>1个元素时,我们分解运行时间如下:

    分解:分解步骤仅仅计算子数组的中间位置,需要常量时间;

    解决:递归地求解两个规模均为n/2的子问题,将贡献2T(n/2)的运行时间;

    合并:一个具有n个元素的数组,MERGE过程时间复杂度为theta(n),记为C(n)=theta(n)。

    给出最坏情况运行时间T(n)的递归式:

    image

    我们已有将证明T(n)=theta(nlgn),注意这里的lgn代表以2为底的对数函数。可以用递归树的方法也可以证明其复杂度。

    既然想开了用截图,那么递归树也来愉快地截图吧……

    image

  • 相关阅读:
    jar包的MANIFEST.MF文件
    Spring AOP无法拦截Controller中的方法
    强制更新 Maven缓存库
    【转】深入理解Java:注解(Annotation)--注解处理器
    Java-Method类常用方法详解
    MySQL下查看用户和建立用户
    【转】从零开始玩转logback
    【转】Java日志框架:logback详解
    【转】配置不当引起高危漏洞?看加密货币交易所如何正确用Spring Boot Actuaotr框架
    Spring MVC Junit4 单元測试 JunitTest
  • 原文地址:https://www.cnblogs.com/batteryhp/p/4654070.html
Copyright © 2011-2022 走看看