数据结构与算法设计(读书笔记):2.算法分析

数学基础:
定义:
数据结构与算法设计(读书笔记):2.算法分析_第1张图片
如果只是小量输入的情况,那么花费大量时间去努力设计聪明的算法恐怕并不值得。因此,好的算法应该是因地制宜的,不能盲目。
算法分析的基本策略是从内部(或最深层部分)向外展开的。
正常的用递归解法求解Fib的算法之所以缓慢,是因为有大量的多余的工作量,重复计算较多,可以通过保留一个简单的数组并使用一个for循环将运行时间降下来。

接下来,我们比较四种不同的求解最大子序列和问题的算法。
算法1。

public static int maxSubSum1(int[] a){
            int maxSum = 0;
            for (int i = 0; i < a.length; i++) {
                for (int j = i; j < a.length; j++) {
                    int thisSum = 0;
                    for (int k = i;  k<= j; k++) {
                        thisSum+=a[k];
                    }
                    if(thisSum>maxSum){
                        maxSum = thisSum;
                    }
                }
            }
            return maxSum;
        }
//该算法的思想比较直接。i从0到length,j从i到length,再用k(位于i与j之间)将i与j之间的sum计算出来。这样取最大的sum即为所求。

时间复杂度为N的3次方。
算法2.

public static int maxSubSum2(int[] a){
            int maxSum = 0;
            for (int i = 0; i < a.length; i++) {
                int thisSum = 0;
                for (int j = i; j < a.length; j++) {
                        thisSum+=a[j];
                    if(thisSum>maxSum){
                        maxSum = thisSum;
                    }
                }
            }
            return maxSum;
        }
    //这里的算法2只是1的一个小改动,将原来的:
    for (int k = i;  k<= j; k++) {
                        thisSum+=a[k];
                    }
    换成了:
    thisSum+=a[j];并将thisSum提出(显然的)
    //事实上也应该是这样。最大子序列和问题的本质,就是每个子序列的首与尾的位置不固定,那么我们只需要循环两次,每次得到确定首尾的sum值,然后取最大就可以了

时间复杂度为N的2次方。

“分治”策略:
把问题分成两个大致相等的子问题,然后递归地对他们求解,这是分。
“治”是将两个子问题的解合到一起,并可能做些少量的附加工作,最后得到整个问题的解。
在我们的例子中,最大子序列只可能在3处出现。1.整个出现在输入数据的左半部2.整个出现在输入数据的右半部。针对这两种情况都可以用递归求解。3.最大和可以通过求出前半部分最大和(包含前半部分最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而得到,然后将这两个和加在一起。
算法3.

public static int maxSubSum3(int[] a,int left,int right){
            //基本情况
            if(left==right){
                if(a[left]>0){
                    return a[left];//最大子序列
                }else{
                    return 0;//不选取该值作为最大子序列的一部分,所以应该返回0
                }
            }

            //分治
            int center = (left+right)/2;
            int leftMaxSum = maxSubSum3(a, left, center);
            int rightMaxSum = maxSubSum3(a, center+1, right);

            int leftBorderSum = 0,leftBorderMaxSum=0;
            //固定一端center,达到前面的最大子序列
            for (int i = center; i >= left; i--) {
                leftBorderSum+=a[i];
                if(leftBorderSum>leftBorderMaxSum){
                    leftBorderMaxSum = leftBorderSum;
                }
            }

            int rightBorderSum = 0,rightBorderMaxSum=0;
            //固定一端center,达到后面的最大子序列
            for (int i = center+1; i <= right; i++) {
                rightBorderSum+=a[i];
                if(rightBorderSum>rightBorderMaxSum){
                    rightBorderMaxSum = rightBorderSum;
                }
            }

            int twoBorderMaxSum = leftBorderMaxSum+rightBorderMaxSum;
            int maxSum1 = Math.max(leftMaxSum,rightMaxSum);
            int maxSum = Math.max(maxSum1, twoBorderMaxSum);
            return maxSum;
        }

时间复杂度为NlogN.都是用递归,Fib低效而本例却比较高效,是因为每次递归降低问题的维度不一样,前者维度下降慢,后者则是对半的下降。

算法4.

public static int maxSubSum4(int[] a){
            int maxSum =0,thisSum = 0;
            for (int i = 0; i < a.length; i++) {
                thisSum+=a[i];
                if(thisSum>maxSum){
                    maxSum = thisSum;
                }
                if(thisSum<0){
                    thisSum = 0;
                }
            }
            return maxSum;
        }
//分析原因,注意,像算法1和算法2一样,j代表当前序列的终点,而i代表当前序列的起点。碰巧的是,如果我们确实不需要知道最佳的子序列在哪里,那么i的使用可以脱离程序进行优化,我们要改进算法2。
//一个重要结论是,如果a[i]是负的,那么它不可能代表最优序列的起点。类似的,任何负的子序列不可能是最优子序列的前缀。

时间复杂度为N。
该算法的一个附带优点是,它只对数据进行一次扫描,一旦a[i]被读入并被处理,他就不再需要被记忆。因此,如果数组在磁盘上,他就可以被顺序读入,在主存中不必存储数组的任何部分。不仅如此,在任意时刻,算法都能对它已经读入的数据给出子序列问题的正确答案(其他算法不具有这种特性)。具有这种特性的算法叫做联机算法(on-line algorithm)。仅需要常量空间并以线性时间运行的联机算法几乎是完美的算法。

运行时间中的对数:
分析算法最混乱的方面集中在对数上,我们已经看到某些分治算法将以NlogN时间运行。除分治算法外,对数最常出现的规律概括为下列一般法则:
如果一个算法用常数时间将问题的大小削减为其一部分(通常是一半),那么该算法就是logN。
另一方面,如果使用常数时间只是把问题减少一个常数(如将问题减少1),那么这种算法就是N的。

具有对数特点的三个例子:
1.对分查找(Binary Search),在数据稳定(即不允许插入操作和删除操作)的应用中,Binary Search非常有用。此时输入数据需要一次排序(前提),但是此后访问就很快了。
2.计算最大公因数的欧几里得算法。

public static long gcd(long M,long N){
            while(N!=0){
                long rem = M%N;
                M = N;
                N = rem;
            }
            return M;
        }

3.幂运算

你可能感兴趣的:(数据结构与算法)