您的位置:首页 > 理论基础 > 数据结构算法

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

2016-04-03 20:50 274 查看
数学基础:

定义:



如果只是小量输入的情况,那么花费大量时间去努力设计聪明的算法恐怕并不值得。因此,好的算法应该是因地制宜的,不能盲目。

算法分析的基本策略是从内部(或最深层部分)向外展开的。

正常的用递归解法求解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.幂运算
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息