《算法导论》学习笔记(2)

引言

本次内容包含第四章分治策略中算法的代码实现

最大子数组问题

最大子数组问题是寻找一个数组A中最大的非空连续子数组。
文中提到了三种解决方法,分别是暴力求解法分治策略,和课后习题中的线性解决方法(动态规划)。下面给出分治策略和动态规划的代码实现。

库引用和结构体定义

#define LEN_OF_ARRAY(a) (sizeof(a) / sizeof(a[0]))
#include <stdio.h>
#include <math.h>
#include <limits.h>

typedef struct
{
    int low;
    int high;
    int sum;
}SubArray;

分治策略

// ***********  merge  ************
SubArray FindMaxCrossSubarray(int A[], int high, int mid, int low){
    int sum = 0;
    int index = mid;
    int maxSumLeft = INT_MIN;
    int maxLeft;
    while(index >= low){
        sum += A[index-1];
        if(maxSumLeft < sum){
            maxSumLeft = sum;
            maxLeft = index;
        }
        index--;
    }

    sum = 0;
    index = mid+1;
    int maxSumRight = INT_MIN;
    int maxRight;
    while(index <= high){
        sum += A[index-1];
        if(maxSumRight < sum){
            maxSumRight = sum;
            maxRight = index;
        }
        index++;
    }

    return SubArray{maxLeft, maxRight, maxSumLeft+maxSumRight};
}

SubArray FindMaxSubarrayMerge(int A[], int high, int low=1){
    if(low == high)
        return SubArray{low, high, A[low-1]};

    int mid = floor((high + low) / 2);
    SubArray subLeft = FindMaxSubarrayMerge(A, mid, low);
    SubArray subRight = FindMaxSubarrayMerge(A, high, mid+1);
    SubArray subMid = FindMaxCrossSubarray(A, high, mid, low);

    if(subLeft.sum > subMid.sum && subLeft.sum > subRight.sum)
        return subLeft;
    if(subRight.sum > subLeft.sum && subRight.sum > subMid.sum)
        return subRight;
    return subMid;    
}

动态规划

其中sumToMax[ i ]记录的是以 i 为底的最大连续子数组
当sumToMax[ i-1 ] <0时,A数组的前 i 项就对sumToMax数组后 i 项的求值没有影响了

// ***********  line dynamic programming  ************
SubArray FindMaxSubarrayDynamic(int A[], int len){
    int sumMax = A[0];
    int start = 0;
    int end = 0;

    int temIndex = 0;
    int sumToIMax[len];
    sumToIMax[0] = A[0];
    for(int i=1; i<len; i++){
        if(sumToIMax[i-1] <= 0){
            sumToIMax[i] = A[i];
            temIndex = i;
        }else{
            sumToIMax[i] = A[i] + sumToIMax[i-1];
        }
        if(sumToIMax[i] > sumMax){
            start = temIndex;
            end = i;
            sumMax = sumToIMax[i];
        }
    }
    
    return SubArray{start+1, end+1, sumMax};
}

主测试函数

int main(){
    int a[] = {6,3,4,-1,-5,-6,20,-9,3};
    // int a[] = {-3,-4};
    SubArray s = FindMaxSubarrayMerge(a, LEN_OF_ARRAY(a));
    printf("%d, from %d to %d\n", s.sum, s.low, s.high);
    s = FindMaxSubarrayDynamic(a, LEN_OF_ARRAY(a));
    printf("%d, from %d to %d\n", s.sum, s.low, s.high);
    return 0;
}

芯片检测问题(4-5)

Diogenes 教授有n个被认为是完全相同的集成电路芯片,原理上它们是可以互相测试的。教授的测试装置一次可容纳二片芯片,当该装置中放有两片芯片时,每片芯片就对另一片进行测试,并报告其好坏。一个好的芯片总能够正确的报告另一片的好坏,但一个坏的芯片的结果是不可靠的。每次的测试的四种可能结果如下:

A芯片报告 B芯片报告 结论
B是好的 A是好的 都是好的,或都是坏的
B是好的 A是坏的 至少一片是坏的
B是坏的 A是好的 至少一片是坏的
B是坏的 A是坏的 至少一片是坏的

a)证明若不少于 n/2 的芯片是坏的,在这种成对测试方式下,使用任何策略都不能确定哪个芯片是好的.
b)假设有多于 n/2 的芯片是好的,考虑从 n 片中找出一片好芯片的问题.证明 n/2 对测试就足以使问题的规模降至近原来的一半.
c)假设有多于 n/2 的芯片是好的,证明好的芯片可用 O(n) 对测试找出.给出并解答表达测试次数的递归式。

  • a) 设有a块好芯片,a+m块坏芯片。其中a块坏芯片将其他的n-a块芯片分成坏的,剩下的m块坏芯片随机分好坏,则无法分开a块好芯片和a块坏芯片。
  • b) c)
  1. n个芯片随机成对测试,总共n/2次,若是后三种情况,两片都丢弃,第四种情况则随机丢弃一片芯片。若是奇数个芯片,则将剩余的那个芯片与每个芯片测试,若有半数为真,则返回该芯片,否则丢弃,花费n次操作。
  2. 因为属于后三种情况的芯片中坏芯片数 >= 好芯片数,所以属于第一种情况的芯片依然满足好芯片数 > 坏芯片数。又因为第一种情况相当于好芯片、坏芯片各自随机丢弃一半的数量,所以剩下的芯片依然满足好芯片数 > 坏芯片数,并且芯片数量减少了至少n/2,成为了规模为之前一半的子问题。再回到1的操作直到只剩一个芯片。
  3. 最糟糕情况下,算法的复杂度的递归式为T(n) = T(n/2) + 3n/2,则T(n) = O(n)

猜你喜欢

转载自blog.csdn.net/LeonShaw_zh/article/details/83064999
今日推荐