1. 程式人生 > >《演算法導論》第四章_讀書筆記

《演算法導論》第四章_讀書筆記

分治思想Divide-and-Conquer

包含三個步驟,分解divide,解決conquer,合併combine

求解遞迴式的演算法效率有三個方法,數學規劃,遞迴樹,主方法

最大子陣列問題

遞迴演算法

最大子陣列的位置只能是三種之一

· 完全位於左子陣列

· 完全位於右子陣列

· 跨越中點

#include <iostream>

using namespace std;

struct MaximumSubarray{
    int low;
    int high;
    int sum;
};

/******************************
func:求跨越中點的最大子陣列
para:A:陣列;low:左下標;mid:中下標;high:右下標
******************************/
MaximumSubarray findMaxCrossingSubarr(int A[],int low,int mid,int high){ int left_sum=(int*)malloc(sizeof(int));//最小負數 int right_sum=0x80000000;//最小負數 int max_left=0; int max_right=0; int sum=0; MaximumSubarray maxSubarr={0}; 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; } } maxSubarr.low=max_left; maxSubarr.high=max_right; maxSubarr.sum
=left_sum+right_sum; return maxSubarr; } /*********************************** func:求給定陣列的最大子陣列 para:A:陣列;low:左下標;high:右下標 ***********************************/ MaximumSubarray findMaxSubarr(int A[],int low,int high){ MaximumSubarray maxSubarrLeft={0}; MaximumSubarray maxSubarrRight={0}; MaximumSubarray maxSubarrCross={0}; if(high==low){ //特殊情況:只有一個元素 maxSubarrLeft.low=low; maxSubarrLeft.high=high; maxSubarrLeft.sum=A[low]; return maxSubarrLeft; } int mid=(low+high)/2; maxSubarrLeft=findMaxSubarr(A,low,mid); maxSubarrRight=findMaxSubarr(A,mid+1,high); maxSubarrCross=findMaxCrossingSubarr(A,low,mid,high); if(maxSubarrLeft.sum>maxSubarrRight.sum&&maxSubarrLeft.sum>maxSubarrCross.sum) return maxSubarrLeft; else if(maxSubarrRight.sum>maxSubarrLeft.sum&& maxSubarrRight.sum>maxSubarrCross.sum) return maxSubarrRight; else return maxSubarrCross; } int main(int argc,char* argv[]){ int A[16]={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7}; MaximumSubarray resMaxSubarr=findMaxSubarr(A,0,15); cout<<"maximum subarray is:"<<endl; cout<<"resMaxSubarr.low:"<<resMaxSubarr.low<<endl; cout<<"resMaxSubarr.high:"<<resMaxSubarr.high<<endl; cout<<"resMaxSubarr.sum:"<<resMaxSubarr.sum<<endl; return 0; }

程式碼來源

演算法效能為 Θ(nlgn),優於暴力求解的Θ(n2)

這裡貼出包含計時器的測試函式程式碼

void test_max_array(void)
{
//  int Data[16]={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
    int* Data=(int*)malloc(sizeof(int)*LENGTH);
    srand((unsigned)time(NULL)); 
    for(int i=0;i<LENGTH;i++)                      //隨機產生隨機數
    {
        int flag=rand()%2;
        int temp=rand()%100;   
        if(flag==0)
            Data[i]=0-temp;
        else
            Data[i]=temp;
    }
    printf("\n隨機產生100個數:\n");
    for(int i=0;i<LENGTH;i++)                     
    {
        if(i%10==0)
            printf("\n");
        printf("%d  ",Data[i]);
    }    

    int *left=(int*)malloc(sizeof(int));
    int *right=(int*)malloc(sizeof(int));

    int start_time=GetTickCount();   //記錄開始時間

    int sum=Find_maximum_subarray(Data, 0, LENGTH-1, left, right);  
    printf("\n\n分治求解答案:\nleft=%d\nright=%d\nsum=%d\n\n",*left,*right,sum);

    Time_calculate(start_time);       //列印結束時間
    start_time=GetTickCount();   //記錄開始時間

    int sum_force=Force_search(Data, 0, LENGTH-1, left, right);
    printf("\n暴力求解答案:\nleft=%d\nright=%d\nsum_force=%d\n\n\n\n",*left,*right,sum_force);

    Time_calculate(start_time);     //列印結束時間。

    free(left);
    free(right);
}

來源

非遞迴的線性問題的演算法(動態規劃)


動態規劃思想:是由上一階段的問題的解(可能被分解為多個子問題)遞推求解出最終問題的解。

動態規劃問題包含兩個部分,一是問題的狀態,二是狀態轉移方程

該問題的演算法思路:

從陣列的左邊界開始,由左至右處理,記錄到目前為止已經處理過的最大子陣列。若已知A[1,j]的最大子陣列,基於如下性質將解擴充套件為A[1,j+1]的最大子陣列:

A[0,j+1]的最大子陣列,要麼是

1)某個子陣列A[i,j+1],(0≤i≤j+1),

2)就是A[1,j]的最大子陣列

在已知A[1,j]的最大子陣列的情況下,可以線上性時間內找出形如A[i,j+1]的最大子陣列。

//尋找A[i,j+1]中最大子陣列,子陣列右下標已經固定
MaximumSubarray FIND_MAX_ARRAY(int A[],int low,int high){
    MaximumSubarray maxSubarr={0};
    if (high==low){
        maxSubarr.low=low;
        maxSubarr.high=high;
        maxSubarr.sum=A[low];
        return maxSubarr;
    }
    maxSubarr.low=high;
    maxSubarr.high=high;
    maxSubarr.sum=A[high];
    int tmp_sum=A[high];
    for (int i=high-1;i>=low;--i){
         tmp_sum+=A[i];
         if(tmp_sum>maxSubarr.sum){
             maxSubarr.sum=tmp_sum;
             maxSubarr.low=i;
         }
    }
    return maxSubarr;
}

/***********************************************
func:求給定陣列的最大子陣列
para:A:陣列;low:起始下標;high:結束下標
***********************************************/
MaximumSubarray FIND_MAXIMUM_SUBARRAY(int A[],int low,int high){
    MaximumSubarray maxSubarr={0};
    if (high==low){
        maxSubarr.low=low;
        maxSubarr.high=high;
        maxSubarr.sum=A[low];
        return maxSubarr;
    }
//從A[0]開始
    maxSubarr.low=low;
    maxSubarr.high=low;
    maxSubarr.sum=A[low];
    MaximumSubarray tmpSubarr={0};

    for (int i=low+1; i<=high;++i)
        tmpSubarr=FIND_MAX_ARRAY(A,low,i);
  //如果A[i,j+1]中最大子陣列大於A[1,j]的最大子陣列
        if(tmpSubarr.sum>maxSubarr.sum)
            maxSubarr=tmpSubarr;
    return maxSubarr;
}

int main(int argc,char* argv[]){
    int A[16]={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
    MaximumSubarray resMaxSubarr=FIND_MAXIMUM_SUBARRAY(A,0,15);
    cout<<"dynamic programming maximum subarray is:"<<endl;
    cout<<"resMaxSubarr.low:"<<resMaxSubarr.low<<endl;
    cout<<"resMaxSubarr.high:"<<resMaxSubarr.high<<endl;
    cout<<"resMaxSubarr.sum:"<<resMaxSubarr.sum<<endl;
    return 0;
}

程式碼來源

其實其中有很多可優化空間

比如判斷當前元素A[j+1]是否為正:

​ 如果為負值,則情況1)不用分析;

​ 如果為正,則計算情況1);

比如判斷當前最大子元素是否為正

如果為負,而當前元素A[j+1]為正,則情況2)不用分析,且計算情況1)時,應該從前一個最大子陣列的最右下標往後的第一個正數位置開始考慮

而如果想達到線性時間的效能,則需要通過快取一些中間結果來實現

具體參考部落格

ps clrs.skanev.com 的演算法好工整啊 ,貼出來

We need to build an array S that holds the maximum subarrays ending on each index of A. That is, S[j] holds information about the maximum subarray ending on j.

We first loop through the input to build S. Afterwards, we do what they suggest in the text. This is n+n=2n=Θ(n).

typedef struct {
    unsigned left;
    unsigned right;
    int sum;
} max_subarray;

max_subarray find_maximum_subarray(int A[], unsigned low, unsigned high) {
//計算最右下標固定的最大子陣列
    max_subarray suffixes[high - low];

    suffixes[0].left = low;
    suffixes[0].right = low + 1;
    suffixes[0].sum = A[low];

    for (int i = low + 1; i < high; i++) {
    //如果上一個最大子陣列是個負數
        if (suffixes[i - 1].sum < 0) {
            suffixes[i].left = i;
            suffixes[i].right = i + 1;
            suffixes[i].sum = A[i];
        } else {
        //如果s[i]大於零,那s[i+1]就是直接把A[i]加進去

            max_subarray *previous = &suffixes[i - 1];
            suffixes[i].left = previous->left;
            suffixes[i].right = i + 1;
            suffixes[i].sum = previous->sum + A[i];
        }

    }

    max_subarray *max = &suffixes[0];

    for (int i = low + 1; i < high; i++) {
        if (max->sum < suffixes[i].sum) {
            max = &suffixes[i];
        }
    }

    return *max;
}

主定理

習題 4.5.5 題目

考慮在某個常數c < 1時的規則性條件af(n/b)<=cf(n),此條件是主定理第三種情況的一部分。舉一個常數a >= 1,b > 1以及一個函式f(n),滿足主定理第三種情況中的除了規則性條件之外的所有條件的例子。

分析與解答

首先f(n)要比nlogba大,若nlogba是多項式函式,那麼f(n)至少是多項式函式;

同時要滿足f(n)是多項式大,如果滿足了多項式大,那麼如果f(n)是多項式函式的話,很容易證明,由於f(n)的最高次比nlogba大,所以af(n/b)最高次的係數a/( blogba + ε ) < 1,所以規則性條件肯定成立,因此f(n)的增長性要比多項式函式大,但是增長性快的函式,f(n/b)影響是決定性的,不論a多大其都會在n足夠大時小於cf(n);

所以考慮使用兩類函式進行復合組成f(n),例如將兩類函式相除,分母函式的增長性大於分子函式的增長性,考慮多項式函式與對數函式相除,這樣f(n/b)可以還原出組成組成f(n)的函式,但是如果多項式函式的次數大於1的話,還是可以找到滿足規則性條件,因為將這類複合函式帶入規則性條件,最終都可以得到(mc - 1)lgn >= mc的形式,如果組成f(n)的多項式函式的次數大於1的話,m就會大於1,可以找到c使(mc-1)大於0,此時必然可以找到n使(mc - 1)lgn>= mc成立。

可見如果是用多項式函式與對數函式相除組成f(n)的話,nlogba得是常數,即a=1,此時因為c< 1,所以c-1小於0,那麼不等式(mc - 1)lgn >= mc不可能成立,即遞迴式就不滿足規則性條件。

例如遞迴式:T(n)=T(n/2)+n2/lgn

來源

T(n) = T(n/2) + n(2−cosn)

思考題4.2 引數傳遞的代價

意思就是在原遞迴式的基礎上加上一個引數傳遞代價,重新計算漸近緊確界

standford algorithm on coursera week 1 note

building machine learning systems with python

機器學習系統設計

refuse to be content

所有演算法都還有優化空間

永遠要問自己

晶片檢測問題

問題:Diogenes 教授有n個被認為是完全相同的VLSI晶片,原則上它們是可以互相測試的.教授的測試裝置一次可測試二片,當該裝置中放有兩片晶片時,每一片就對另一片作 測試並報告其好壞.一個好的晶片總能夠正確的報告另一片的好壞,但一個壞的晶片的結果就是不可靠的

a)證明若少於 n/2 的晶片是壞的,在這種成對測試方式下,使用任何策略都不能確定哪個晶片是好的.
b)假設有多於 n/2 的晶片是好的,考慮從 n 片中找出一片好晶片的問題.證明 n/2 對測試就足以使問題的規模降至近原來的一半.
c)假設有多於 n/2 的晶片是好的,證明好的晶片可用 O(n) 對測試找出。

思路:

兩兩配對進行檢測(檢測次數為n/2下界),那麼結果中有

a個oo b個xx c個ox

假設剩下1個

那麼a>b+1

我們選出那些結果為1晶片,

因為a>b,所以用這a+b個晶片去檢測最後那個零頭,那麼如果超過一半說是壞的,那麼最後能檢測出正確結果

附錄B

假設R是非空集合A中的一個關係,並且具有對稱性和傳遞性。有人斷定R是一個等價關係,其推理如下:
“對a,b∈A,從a R b得b R a,又從傳遞性得a R a,因而R有自反性,故為等價關係,”(題目問他的推理對嗎?)

這個推論是錯誤的

解答:

由對稱性: 如果存在aRb,那麼推出 bRa
但自反性的定義是: 任意a, aRa. 這裡推不出自反性

神舉例:

A能和B接吻 B能和C接吻 那麼 A能和C接吻 傳遞;
A能和B接吻 B能和A接吻 對稱;
A能吻他自己否??????

附錄C

c 2.6 描述一個以整數a和b為輸入的過程,其中0