【算法】第四課學習筆記

封面

1、求局部最大值

1. 題目

給定一個無重複元素的數組A[0…N-1],求找到一個該數組的局部最大值。規定:在數組邊界外的值無窮小。即:A[0]>A[-1],A[N-1] >A[N]。c++

顯然,遍歷一遍能夠找到全局最大值,而全局最大值顯然是局部最大值,時間複雜度爲O(N)。算法

能否有更快的辦法?數組

2. 分析

首先給出「高原數組」定義(鄒博老師自創的)。函數

定義:若子數組Array[from,…,to]知足優化

  • Array[from]>Array[from-1]
  • Array[to]>Array[to+1]

稱該子數組爲「高原數組」。spa

例如對於數組3,5,1,2,6,7,14,由於2<6,14>4,能夠認爲6,7,14就是其中的一個高原數組。
用圖像形象地表示爲:3d

高原數組圖示

若高原數組長度爲1,則該高原數組的元素爲局部最大值。code

所以尋找局部最小值的算法就變成了尋找長度爲1的高原數組。
(這裏特別注意!題中只要求找到一個局部最大值便可!)blog

算法描述:
使用索引left、right分別指向數組首尾,根據定義,該數組爲高原數組。排序

  • 求中點mid=(left+right)/2
  • A[mid]>A[mid+1],子數組A[left…mid]爲高原數組
  • 丟棄後半段:right=mid
  • A[mid+1]>A[mid],子數組A[mid…right]高原數組
  • 丟棄前半段:left=mid+1
  • 遞歸直至left==right

該算法的時間複雜度爲O(logN),最重要的是給了咱們一些思惟上的啓發,即特別大的數據,我並不須要徹底遍歷一遍,就能知道這個數據中的某些有效的局部特徵。

3.代碼

/**
 * 尋找一個局部最小值
 */
int local_maximum(const int* a, int n){
    int left = 0;
    int right = n - 1;
    int mid;
    while(left < right){
        mid = (right - left) / 2 + left; 
        // 防溢出,等價於mid = (left + right) / 2 
        cout<<a[mid]<<endl;
        if(a[mid] > a[mid+1])
            right = mid;
        else
            left = mid + 1;
    }
    return a[left];
}

2、子集和數問題

1. 題目

已知數組A[0…N-1],給定某數值sum,找出數組中的若干個數(能夠爲0個或n個),使得這些數的和爲sum。
(假定數組中的元素都大於0:A[i]>0)

例如,
對於數組1,2,3,4,5,給定sum=10,則知足條件的若干個數有:
1, 2, 3, 4
1, 4, 5
2, 3, 5

2. 分析與代碼

顯然,這個問題是一個NP徹底問題。

針對此題,能夠設置布爾向量x[0…N-1](標記數組)來表示取了哪一個元素,x[i]=0表示不取A[i],x[i]=1表示取A[i],與0-1揹包問題的設置方法相似。

(1). DFS遞歸遍歷

首先上場的是DFS遞歸遍歷,利用函數的參數i表示當前進行到的位置,用has表示已經加入的元素當前的和,代碼以下:

int a[] = {1,2,3,4,5};
int n = sizeof(a) / sizeof(int);
int sum = 10;

/**
 * 直接遞歸
 * x[]爲最終解,i爲考察a[i]是否加入,has表示當前的和
 */
void enum_sum_number(bool *x, int i, int has){
    if(i>=n) return;
    if(has + a[i] == sum){
        x[i] = true;
        print(a,x); // 自定義的輸出函數
        x[i] = false;
    }
    x[i] = true;
    enum_sum_number(x, i + 1, has + a[i]);
    x[i] = false;
    enum_sum_number(x, i + 1, has);
}

(2). 分支限界法

而後考慮對DFS進行優化,便有了分支限界法。
考慮如何對分支進行限界,前提:數組A[0…N-1]的元素都大於0。
考察向量x[0…N-1],假定已經肯定了前i個值,如今要斷定第i+1個值x[i]爲0仍是1。
假定由x[0…i-1]肯定的A[0…i-1]的和爲has;A[i,i+1,…N-1]的和爲residue(簡記爲r);

  • has + a[i] ≤ sum而且 has + r ≥ sum:x[i]能夠爲1;
  • has + (r - a[i]) >= sum:x[i]能夠爲0;

(注意,在編寫代碼進入分支的時候,要注意避免重複進入分支!)

代碼以下:

/**
 * 分支限界
 */
void enum_sum_number2(bool *x, int i, int has, int residue){
    if(i>=n) return;
    if(has + a[i] == sum){
        x[i] = true;
        print(a,x);
        x[i] = false;
    } else if(has + residue >= sum && has + a[i] <= sum){
        // 注意此處是 else if ,由於若進入了 has + a[i] == sum分支,
        // 意味着已經選了a[i]了,因此此處不須要重複選擇a[i]了
        x[i] = true;
        enum_sum_number2(x, i + 1, has + a[i], residue - a[i]);
    }
    if(has + residue - a[i] >= sum){
        x[i] = false;
        enum_sum_number2(x, i + 1, has, residue - a[i]);
    }
}

數理邏輯的重要應用:分支限界的條件

思考:

  • 分支限界的條件是充分條件嗎? (不是,是必要條件)
  • 在新題目中,如何發現分支限界的條件。

(學會該方法,比此問題自己更重要)

(3). 考慮負數的狀況

給出一個例子:數組 -3,-5,-2,4,2,1,3 , 給定sum = 5,
符合要求的數有
-3, -2, 4, 2, 1, 3
-3, 4, 1, 3
-5, 4, 2, 1, 3
-2, 4, 2, 1
-2, 4, 3
4, 1
2, 3

DFS由於是枚舉,確定可以獲得正確解,關鍵是如何對負數的狀況進行分支限界?

下面給出一種方法:

可對整個數組A[0…N-1]進行正負排序,使得負數都在前面,正數都在後面(只須要保證負數部分在前面便可,不須要保證負數部份內部也是有序的),使用剩餘正數的和做爲分支限界的約束:

  • 若是A[i]爲負數:若是所有正數都算上還不夠,就不能選A[i];
  • 若是遞歸進入了正數範圍,按照數組是全正數的狀況正常處理;

代碼以下:

/**
 * 一種正負排序的實現,並計算negative和positive
 */
void positive_negative_sort(int a[], int n, int &negative, int &positive){
    int k = 0;
    negative = positive = 0;
    for(int i=0; i<n; i++)
        if(a[i] < 0){
            negative += a[i];
            swap(a[i], a[k]);
            ++k;
        } else
            positive += a[i];
}

/**
 * 含有負數的分支限界
 */
void enum_sum_number3(bool x[], int i, int has, int negative, int positive){
    if(i >= n) return;
    if(has + a[i] == sum){
        x[i] = true;
        print(a,x);
        x[i] = false;
    } 
    if(a[i] > 0){
        // 正數的狀況
        if(has + positive >= sum && has + a[i] <= sum){
            x[i] = true;
            enum_sum_number3(x, i + 1, has + a[i], negative, positive - a[i]);
            x[i] = false;
        }
        if(has + positive - a[i] >= sum){
            x[i] = false;
            enum_sum_number3(x, i + 1, has, negative, positive - a[i]);
        }
    } else {
        // 負數的狀況
        if(has + x[i] + positive >= sum){
            x[i] = true;
            enum_sum_number3(x, i + 1, has + a[i], negative - a[i], positive);
            x[i] = false;
        }
        if(has + negative <= sum && has + positive >= sum){
            x[i] = false;
            enum_sum_number3(x, i + 1, has, negative - a[i], positive);
        }
    }
}
相關文章
相關標籤/搜索