第二章 算法分析

算法是为求解一个问题需要遵循的,被清楚指定的简单指令的集合,判断一个算法的好坏,其平均运行时间一般是难以得到的,但是最坏情况的运行时间容易得出,一般用最坏情况下的运行时间来衡量一个算法的好坏。

1 最大子序列和问题

这里实验了三个算法复杂度分别是O(N2N^2)、O(NlogN\log{N})和O(N)的算法

1.1 二次解法

即进行两次遍历求最大子序列和的方式进行处理。

/**
     * 两个for循环,时间大小为O(N*N)
     * @param arr
     * @return
     */
    public static int firstMethod (int [] arr) {
       int maxSum = 0;
       for (int i = 0 ; i < arr.length; i++ ) {
           int thisSum = 0;
           for (int j = i; j < arr.length; j++) {
               thisSum += arr[j];
               if (thisSum > maxSum) {
                   maxSum = thisSum;
               }
           }
       }
       return maxSum;
    }

1.2 二分法

  • 分治法解决最大子序列和问题
    • 1、将数组分成两部分,找出前半部分的最大子序列和(max1)和后半部分的最大子序列和(max2)
    • 2、找出包含最后一个元素的前半部分的最大序列和
    • 3、找出包含第一个元素的后半部分的最大序列和
    • 4、将第2步和第3步中的两个和相加,得出max3
    • 5、返回max1、max2和max3三个数中的最大者
/**
     * 分治法解决最大子序列和问题
     * 1、将数组分成两部分,找出前半部分的最大子序列和(max1)和后半部分的最大子序列和(max2)
     * 2、找出包含最后一个元素的前半部分的最大序列和
     * 3、找出包含第一个元素的后半部分的最大序列和
     * 4、将第2步和第3步中的两个和相加,得出max3
     * 5、返回max1、max2和max3三个数中的最大者
     * @param arr 数组
     * @param left 开始下标
     * @param right 结束下表
     * @return 返回传入数组的最大子序列和
     */
    public static int divide (int [] arr, int left, int right) {

        //
        if (left == right) {
            if (arr[left] > 0) {
                return arr[left];
            } else {
                return 0;
            }
        }
        int center = (left + right) / 2;
        int maxLeftSum = divide(arr, left, center);
        int maxRightSum = divide(arr, center + 1, right);

        // 找出包含最后一个元素的前半部分的最大序列和
        int maxLeftBorderSum = 0;
        int thisLeftSum = 0;
        for (int i = center; i >= left; i--) {
            thisLeftSum += arr[i];
            if (thisLeftSum > maxLeftBorderSum) {
                maxLeftBorderSum = thisLeftSum;
            }
        }

        // 找出包含的一个元素的后半部分的最大序列和
        int maxRightBorderSum = 0;
        int thisRightSum = 0;
        for (int i = center + 1; i <= right; i++) {
            thisRightSum += arr[i];
            if (thisRightSum > maxRightBorderSum) {
                maxRightBorderSum = thisRightSum;
            }
        }

        // 返回maxLeftSum、maxRIghtSum、maxLeftBorderSum + maxRightBorderSum三个数中的最大者
        int max = maxLeftSum;
        if (maxRightSum > maxLeftSum) {
            max = maxRightSum;
        }
        if (maxLeftBorderSum + maxRightBorderSum > max) {
            max = maxLeftBorderSum + maxRightBorderSum;
        }
        return max;
    }

这里来看一下这个二分法的算法时间分析,令T(N)是求解大小为N的最大序列和问题所花费的时间;两个递归行,每行花费T(N/2)时间,两个循环刚好将整个数组遍历一次,即可以看成花费了O(N)的时间,最后得出结论T(N) = 2T(N/2) + N,
求解这个方程:
T(N) = 2T(N/2) + N, 两边同时除以N,得到,T(N)N\frac{T(N)}{N} = T(N/2)N/2\frac{T(N/2)}{N/2} + 1,依次可以得到这些等式:

  • T(N/2)N/2\frac{T(N/2)}{N/2} = T(N/4)N/4\frac{T(N/4)}{N/4} + 1
  • T(N/4)N/4\frac{T(N/4)}{N/4} = T(N/8)N/8\frac{T(N/8)}{N/8} + 1
  • T(2)2\frac{T(2)}{2} = T(1)1\frac{T(1)}{1} + 1
    左边从N到2,一共有logN个这样的的等式,将logN个这样的等式左右两边分别相加,可以得到,T(N)N\frac{T(N)}{N} = T(1)1\frac{T(1)}{1} + logN, 从而会有:
    T(N) = NlogN + N

1.3 最优解法

只需要遍历一次数组,即可获取最大子序列和

public static int bestMethod (int [] arr) {
        int maxSum = 0;
        int thisSum = 0;
        for (int j = 0; j < arr.length; j++) {
            thisSum += arr[j];
            if (thisSum > maxSum) {
                maxSum = thisSum;
            } else if (thisSum < 0) { // 第i个数到第j个数的序列和小于0时,则可以舍弃这些数了,循环可以直接从j+1开始
                thisSum = 0;
            }
        }
        return maxSum;
    }

这个算法实际上是改进的算法1,理解这个算法最关键的点在于,在算法1的基础上,当AiA_iAjA_j之间的序列和小于0时,那么循环就可以直接推进到Aj+1A_{j+1}.

2、欧几里得算法

计算两个整数的最大公因数,该算法的关键在于算法表述连续计算余数,并把余数赋给n,直到余数是0为止

public static int gcd (int m, int n){

        while (n != 0) {
            int rem = m % n;  // rem 表述余数
            m = n;
            n = rem;
        }

        return m;
    }

3、二分法求幂运算

二分法求xnx^n的值, 根据指数的运算可以得出:xnx^n = xn/2xn/2x^{n/2} * x^{n/2} = (xx)n/2({x*x})^{n/2} ,即可以看成把求解xnx^n的问题大小缩小了一般,当然,在程序中,当n为奇数时,xnx^n = (xx)(n1)/2x({x*x})^{(n - 1)/2}* x ,

public static long pow(long x, int n) {

        if (n == 0) return 1;
        if (n == 1) return x;
        if (n%2 == 0) {
            return pow(x * x, n/2);
        } else {
        	// 当n为奇数时,n/2 的值与(n -1)/ 2的相同
            return pow(x * x, n/2) * x;
        }
    }
发布了3 篇原创文章 · 获赞 3 · 访问量 103
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览