第二章 算法分析

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

1 最大子序列和问题

这里实验了三个算法复杂度分别是O( N 2 N^2 N2)、O(N log ⁡ N \log{N} logN)和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} NT(N) = T ( N / 2 ) N / 2 \frac{T(N/2)}{N/2} N/2T(N/2) + 1,依次可以得到这些等式:

  • T ( N / 2 ) N / 2 \frac{T(N/2)}{N/2} N/2T(N/2) = T ( N / 4 ) N / 4 \frac{T(N/4)}{N/4} N/4T(N/4) + 1
  • T ( N / 4 ) N / 4 \frac{T(N/4)}{N/4} N/4T(N/4) = T ( N / 8 ) N / 8 \frac{T(N/8)}{N/8} N/8T(N/8) + 1
  • T ( 2 ) 2 \frac{T(2)}{2} 2T(2) = T ( 1 ) 1 \frac{T(1)}{1} 1T(1) + 1
    左边从N到2,一共有logN个这样的的等式,将logN个这样的等式左右两边分别相加,可以得到, T ( N ) N \frac{T(N)}{N} NT(N) = T ( 1 ) 1 \frac{T(1)}{1} 1T(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的基础上,当 A i A_i Ai A j A_j Aj之间的序列和小于0时,那么循环就可以直接推进到 A j + 1 A_{j+1} Aj+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、二分法求幂运算

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

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;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值