数据结构与算法分析第二章读书笔记

来源:互联网 发布:2016中国海外并购数据 编辑:程序博客网 时间:2024/04/29 08:32
这本书第二章引发了一个问题,求最大子序列和,并给出了四种解题算法进行对比。问题描述:给定一个数组,求出该数组的最大子序列。

算法1

暴力搜索:即找出所有的子序列,并对其求和来与最大值比较。时间复杂度为O(n3);

int maxSum1(int sum[],int start,int end){    int i,j,k;    int maxSum = 0;    int tmpSum;    for(i = start;i<end;i++){        for(j = i;j<end;j++){            tmpSum = 0;            for(k = i;k<j;k++)                tmpSum+=sum[k];            if(tmpSum>maxSum)                maxSum = tmpSum;        }    }    return maxSum;}

算法2

对算法1进行优化,发现,在最内层中,有大量的工作是在做重复的事情,因此将其优化为如下:时间复杂度为O(n2)

int maxSum2(int sum[],int start,int end){        int i,j,k;    int maxSum = 0;    int tmpSum;    for(i = start;i<end;i++){        tmpSum = 0;        for(j = i;j<end;j++){            tmpSum+=sum[j];            if(tmpSum>maxSum)                maxSum = tmpSum;        }    }    return maxSum;}

算法3

使用二分法,将原来的规模变成子问题的一半,然后再去求解。运行时间为T(N) = 2*T(N/2)+O(N);时间复杂度为O(NlogN);

int maxSum3(int sum[],int start,int end){    int leftMaxSum,rightMaxSum;    int leftBorderSum,rightBorderSum;    int leftMaxSubSum,rightMaxSubSum;    if(end-start==1){        if(sum[start]>0)            return sum[start];        else            return 0;    }    int mid = (start+end)/2;    leftMaxSum = maxSum3(sum,start,mid);    rightMaxSum = maxSum3(sum,mid,end);    //mid     leftMaxSubSum = 0;rightMaxSubSum = 0;    leftBorderSum = 0;rightBorderSum = 0;    int tmp = mid;    while(tmp>=start){        leftBorderSum+=sum[tmp--];        if(leftBorderSum>leftMaxSubSum)            leftMaxSubSum = leftBorderSum;    }    tmp = mid+1;    while(tmp<end){        rightBorderSum+=sum[tmp++];        if(rightBorderSum>rightMaxSubSum)            rightMaxSubSum = rightBorderSum;    }    int midSum = leftMaxSubSum+rightMaxSubSum;    //取三者最大值    int tmpSum = leftMaxSum>rightMaxSum?leftMaxSum:rightMaxSum;    return tmpSum>midSum?tmpSum:midSum;}

算法4

我最多想到算法3,算法4是无论如何都没想到的,算法4的思想就是类似于一个数据流,然后原来的问题变成了从何处截流。当某处的值(或某处以前的值)为负数时,我肯定不会希望将这部分加入到子序列中(会拉低最大值)只有当子序列和非负时,继续下去,然后与最大值比对,看看是否超过最大值。

int maxSum4(int sum[],int start,int end){    int i;    int tmpSum = 0;    int maxSum = 0;    for(i = start;i<end;i++){        tmpSum+=sum[i];        if(tmpSum>maxSum)            maxSum = tmpSum;        if(tmpSum<0)            tmpSum = 0;    }    return maxSum;}

果然是很神奇的。

最后还有几个很有意思的结论,很希望与大家分享:
我在进行算法分析时,最头痛的就是分析log,不知道怎么计算得来。上面提到的二分法,时间复杂度为O(nlogn)。书上对对数常见的规律,归纳如下:
1、如果一个算法用常数时间O(1)将问题大小削减为其一部分(典型的1/2),然后此问题的时间复杂度为O(logn).
2、如果使用常数时间将问题大小减少一个常数(如1)则时间复杂度为O(n).

有几种典型的O(logn)的算法例子。
1、对分查找:对于一个有序数组,对数组查找某个元素,若找到,返回下标,若没有返回-1;

int binarySearch(ElementType A[],Element x,int N){    int low=0,high=N-1;    int mid;    while(low<=high){        mid = (low+high)/2;        if(A[mid]==x)            return mid;        else if(A[mid]>x){            high= mid-1;        }else{            low = mid+1;        }    }    return -1;}

2、欧几里得算法
欧几里得算法用于求解两个数的最大公约数,利用的是公式gcd(a,b) = gcd(b,a%b).
代码如下:

int gcd(int m,int n){    int tmp;    while(n>0){        tmp = m%n;        m = n;        n = tmp;    }    return m;}

对于这道题,我们也许可以模糊的知道大概是log级的算法,但是如何证明却实在是很困难。这里面的时间复杂度等同于计算循环的次数。

定理1:如果M>N,则M mod N 《M/2;
证明:若N<=M/2,则余数一定小于N,则M mod N《M/2; 若N》M/2,M mod N = M-N 《M/2;证毕;

由上述定理可知:gcd(m,n)=gcd(n,m%n)=gcd(m%n,n%(m%n);可见,只要2次gcd操作,就可以将n降为原来的一半,因此欧几里得算法时间复杂度确实为O(logn)

3、幂运算
处理一个整数的幂,计算X^N最直接的算法计算直接自乘,但是时间复杂度为O(N),这里可以优化的地方在于,可以减少多次重复计算量。比如,X^N = X^(N/2) * X^(N/2);而计算出第一个来之后,第二个直接相乘而不必再重复计算。

long int pow(int x,unsigned int N){    if(N==1)        return X;    if(N==0)        return 1;    if(N%2==0)        return pow(x*x,N/2);    else        return pow(x*x,(N-1)/2)*x;}
0 0
原创粉丝点击