演算法導論_第四章_分治策略

chudongfang2015發表於2016-06-28

演算法導論_第四章_分治策略

分治的三個步驟:

分解:將問題劃分為一些子問題,子問題的形式與原問題一樣,只是規模更小。

解決:遞迴的求解出子問題。如果子問題足夠小,則停止遞迴,直接求解

合併:將子問題的解組合成原問題的解。


最大子陣列問題:

給定陣列A,尋找A的和最大的非空連續子陣列。


可以利用暴力求解,其為Ω(n^2)

這裡利用分治法解決,其時間複雜度為Θ(n*lg(n))

既然是分治,即肯定要把陣列分開,其有三種情況:

1.完全位於左邊的陣列

2.完全位於右邊的陣列

3.跨越了中點


下面給出具體程式碼,已經加上詳細註釋:

/*************************************************************************
	> File Name: FIND_MAXIMUM_SUBARRAY.cpp
	> Author:chudongfang 
	> Mail:1149669942@qq.com 
	> Created Time: 2016年06月24日 星期五 08時53分20秒
 ************************************************************************/

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;


void FIND_MAX_CROSSING_SUBARRAY(int A[],int low,int mid ,int high);
void FIND_MAXIMUM_SUBARRAY(int A[],int low,int high);

int _low,_high,_sum;

int main(int argc,char *argv[])
{
    int A[1000]={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
    FIND_MAXIMUM_SUBARRAY(A,0,15);
    printf("%d %d %d",_low,_high,_sum);
    
    return 0;
}
/***********************************
 * 函式功能:得到最大陣列
 * 引數:
 * 1.陣列
 * 2.陣列最左邊座標
 * 3.陣列最右邊座標
 * *********************************/

void FIND_MAXIMUM_SUBARRAY(int A[],int low,int high)
{
    int left_low,left_high,right_low,right_high;
    int left_sum,right_sum;
    int cross_low,cross_high,cross_sum;
    if(low==high)
    {
        _low=low;
        _high=high;
        _sum=A[low];
        return;
    }
    
    int mid=(low+high)/2;//分割點 
    
    FIND_MAXIMUM_SUBARRAY(A,low,mid);//找到第一種情況的最大陣列
    left_low =_low;
    left_high=_high;
    left_sum =_sum;
    
    FIND_MAXIMUM_SUBARRAY(A,mid+1,high);//找到第二種情況的最大陣列
    right_sum=_sum;
    right_high=_high;
    right_low=_low;

    FIND_MAX_CROSSING_SUBARRAY(A,low,mid,high);//找到第三種情況的最大陣列
    cross_low=_low;
    cross_high=_high;
    cross_sum=_sum;
   //比較三種情況,哪種情況較優返回哪種情況
    if(left_sum>=right_sum&&left_sum>=cross_sum)
    {
        _low=left_low;
        _high=left_high;
        _sum=left_sum;
    }
    else if(right_sum>=left_sum&&right_sum>=cross_sum)
    {
        _low =right_low;
        _high=right_high;
        _sum =right_sum;
    }
    else
    {
        _low =cross_low;
        _high=cross_high;
        _sum =cross_sum;
    }
    return ;
}


/**********************************
 * 函式功能:尋找跨越中點的最大子陣列
 * 引數:
 * 1.陣列
 * 2.陣列最左邊
 * 3.陣列中點
 * 4.陣列最右邊
 * *****************************/
void FIND_MAX_CROSSING_SUBARRAY(int A[],int low,int mid ,int high)
{
    int left_sum=-INF;//左邊的最大陣列,從中點開始
    int right_sum=-INF;//右邊的最大陣列,從中點開始
    int sum=0;
    int max_left,max_right;//最大陣列左邊座標,最大陣列右邊座標
    
    for(int i=mid;i>=low;i--)//左邊最大陣列求解
    {
        sum+=A[i];
        if(sum>left_sum)
        {
            left_sum=sum;
            max_left=i;
        }
    }

    sum=0;
    for(int i=mid+1;i<=high;i++)//右邊最大陣列求解
    {
        sum+=A[i];
        if(sum>right_sum)
        {
            right_sum=sum;
            max_right=i;
        }
    }

    //利用全域性變數,代替return 
    _low=max_left;                 
    _high=max_right;             
    _sum=right_sum+left_sum;     
    return ;
}





我們來分析一下其時間複雜度:

可以看出,其把一個原問題分解為兩個子問題,所以2T(n/2)

又其中的尋找 跨越中點的最大子陣列的時間複雜度為Θ(n^2)

所以T(n)=2*T(n/2)+Θ(n^2)

所以根據上幾節的知識,其時間複雜度為Θ(n*lg(n))




下面再來介紹一個例子:


矩陣乘法的Strassen演算法


對於一個矩陣乘法其可以同過一下一個簡單方法實現:


/*************************************************************************
	> File Name: Strassen.cpp
	> Author:chudongfang 
	> Mail:1149669942@qq.com 
	> Created Time: 2016年06月27日 星期一 09時55分03秒
 ************************************************************************/

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
void SQUARE_MATRIX_MULTIPLY(int A[][100],int B[][100],int C[][100],int n);
int main(int argc,char *argv[])
{

    int A[100][100];
    int B[100][100];
    int C[100][100];
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&A[i][j]);

    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&B[i][j]);
    SQUARE_MATRIX_MULTIPLY(A,B,C,n);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            printf("%4d",C[i][j]);
        printf("\n");
    }


    return 0;
}
void SQUARE_MATRIX_MULTIPLY(int A[][100],int B[][100],int C[][100],int n)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            C[i][j]=0;
            for(int k=1;k<=n;k++)
            {
                C[i][j]+=A[i][k]+B[k][j];
            }
        }
    }
}




其時間複雜度為

Θ(n^3);

下面採用分治方法計算:

由於該陣列分治操作相對複雜,這裡先給出虛擬碼,以後有時間再給出相應程式碼:




跟據程式碼可以看出其遞迴式為:

T(n)=8*(n/2)+Θ(n^2)


則根據主方法計算出其時間複雜度為Θ(n^3);

所以其分治並不能降低其時間複雜度


下面介紹一個可以降低其時間複雜度的方法

Strassen方法

其演算法核心思想是利用空間換換時間,利用加減法減少遞迴次數,使其遞迴次數由8次降低到了7次。減少一次的代價

可能是額外的幾次n/2*n/2矩陣的加法,但只是常數次。


下面給出其虛擬碼:







其根據加減法進行一些轉換,最後的結果還是不變的。

這裡給出書上的解釋:











求解遞迴式

這裡重點為主方法,其運用最廣

1.代入法求解遞迴式

思想:

(1)猜測解的形式

(2)用數學歸納法,求出解中的常數,並證明解是正確的。

在這裡,先跟據情況猜測出解的大概情況,然後代入,根據前面的對漸進符號的定義最終求出常數符合條件,則其等

式成立。


2.用遞迴樹求解遞迴式

在遞迴樹中,每個節點表示一個單一的子問題的代價,子問題對應某次遞迴函式呼叫。我們將樹中每層代價求和,得

到每層代價,然後將所有層的代價求和,得到所有層次的遞迴呼叫的總代價。


3.利用主方法求解遞迴式

主方法為如下形式的遞迴式提供了一種“菜譜”式的求解方法

T(n)=a*T(n/b)+f(n)

其中a>=1b>1是常數,f(n)是漸進正函式。


主定理


a>=1b>1是常數,f(n)是一個函式,T(n)是定義在非負整數上的遞迴式:

T(n)=a*T(n/b)+f(n)

其中我們將n/b解釋為向上取整或向下取整,那麼T(n)有如下漸進界:


1.若對某個常數e>0f(n)=O(n^ log(b) a-e)則,T(n)=Θ(n^log(b)a)


2.f(n)=Θ(n^log(b)a)T(n)=Θ(n^log(b)a*lg(n))


3.若對某個e>0f(n)=Ω(n^(log(b)a-e))其對某個常數c<1和所有足夠大的na*f(n/b)

<=c*f(n),T(n)=Θ(f(n))


簡明一點,就是拿f(n)和函式n^(log(b)a)比較,

如果n^(log(b)a)更大,就滿足情況1其解為:T(n)=Θ(n^log(b)a)


若函式f(n)更大,如情況3則解為:T(n)=Θ(f(n))


如果兩個函式大小相當就為情況2,其解為

T(n)=Θ(n^log(b)a*lg(n))

以上比價都必須滿足多項式大於或小於


這裡舉幾個例子


a=9 b=3;

f(n)=n

應用情況1,所以其為Θ(n^2)


a=1 b=3/2 f(n)=1

應用於情況2所以其為Θ(lgn)



a=3 b=4 f(n)=n*lg(n)

應用於情況3所以其為Θ(n*lg(n))


a=2 b=2 f(n)= Θ(n)

應用於情況2所以其為Θ(n*lg(n))




a=8 b=2 f(n)= Θ(n^2)

應用於情況1所以其為Θ(n^3)




a=7 b=2 f(n)= Θ(n^2)

應用於情況1所以其為Θ(n^lg(7))


總結一下,其用法,比較f(n)n^(log(b)a),

如果不相同,則取其較大的那個,


如果相同 ,則取log(b)a再乘上lg(n)



下面還有對主定理的證明內容,由於我們先只是會運用主定理,這裡先不證,留到以後

進階時在進行分析。



相關文章