divide and conquer

来源:互联网 发布:国产机器人仿真软件 编辑:程序博客网 时间:2024/04/29 03:13

刚刚被老妈拉出去健身,所以更的晚了一些。。。


今天这部分是只有视频里面有书上没有的

首先回忆一下上个话题,我们为什么要讲递归式,原因就是分治法中要用到递归式来获得其复杂度。从而评判算法的好坏。

对于一个递归式: T(n) = aT(n/b)+Theta(1)

a表示子问题的个数,n/b表示子问题的大小,Theta(1)表示合并子问题的时候的开销

这里需要注意一点,子问题不能是有重复的情况,比如说算斐波那契数列,用分治法是指数级别的开销(Theta(phi^n),其中phi为黄金分割数),用自底向上的递推则只有Theta(n)的开销。说到斐波那契数列,其实有小于O(n)的算法,一会儿总结里会提到。


接下来写个总结吧:

merge sort

basic idea是先把一个数组分成两半,分别进行merge sort(复杂度是2*O(n/2)),然后把已经merge好的两个数组merge了(复杂度是Theta(n)).

写出递归式是T(n) = 2T(n/2)+Theta(n)

显然,这里我们的两个T(n/2)虽然看上去一样,但并非重复计算,而是分别对两半进行merge,故而不存在冗余计算的情况.

这个大家应该非常熟悉,master method 告诉我们复杂度是nlogn

binary search

这个大家肯定都写过,也能脱口而出它的复杂度是logn

但是既然我们是钻研算法的人,肯定不能满足于此吧。

我先给出它的递归式:T(n) = T(n/2)+Theta(1)

T(n)是指对n个数进行二分查找的复杂度, T(n/2)是指对n/2个数进行二分查找的复杂度,Theta(1)是比较要查找的数和数组中的第n/2个数的运算复杂度

同理,master method 告诉我们上面的递归式的复杂度是logn。

powering a number

这也是很经典的问题,简单的说就是假设计算机不会算乘方,你写一个函数计算x^n。

一个最简单明了的想法就是这样写

int f(float x, int n){    if (n != 1) return x*f(x,n-1);    return x;}
但是稍微想想就会发现我们这里有很多重复计算的地方。比如说,你算x^4,其实只需要x^2再算平方就可以了,并不需要x^3。

这个算法的复杂度是多少呢?O(n),虽然不算大,但我们有更好的。

正如我刚刚所说,算x^n只需要算x^(n/2)或者算x^(n-1)/2就可以了,这取决于n的奇偶性。

我给出它的计算式:

x^n = x^(n/2)*x^(n/2) if n is even

x^n = (x^(n-1)/2)*(x^(n-1)/2)*x if n is odd

再给出它的递归式(这里我们并不在意它的取整情况):

T(n) = T(n/2)+Theta(1)

它的复杂度是logn

给出代码

int f(float x, int n){    if(n == 1) return x;    if(n%2 == 0) {        int temp = f(x,n/2);        return temp*temp;    }    else {        int temp = f(x,(n-1)/2);        return temp*temp*x;    }}

斐波那契数列

类似于上面的问题,简单的做法是递归的计算

f(n) = f(n-1) + f(n-2)

如果你愿意画一下递归树会发现里面重复计算的子树太多了,简直一直都在重复计算。

        事实上这个复杂度是指数级别的,高达O(phi^n),其中phi = (sqrt(5)-1) / 2

但如果我们顺序递推f(n),每次要计算f(n)时f(n-1)和f(n-2)都已经计算好了。这个计算复杂度是O(n)。

事实上有更牛X的方法,但是一般人估计想不到了。斐波那契数其实有个性质,就是round(phi^n/sqrt(5))就是f(n)。这样可以用我们刚才说的计算数的power的方法计算f(n),复杂度是logn。是不是很厉害呢?

不幸的是由于种种原因,这种算法并不能真正在计算机上实现,因为现在的计算机都是浮点型的计算机,根号5和phi其实都是被一些固定位数的浮点数来表示,在计算过程中会失去一些位,结果就是取整的时候得不到正确的答案。

最后教授介绍了一种复杂度为log(n)且可行的方法。((F(n+1), F(n)) (F(n), F(n-1))) = ((1,1) (1,0))^n,这是一个用矩阵来计算的方法,我给出自己写的实例代码。

可以用一维矩阵替代二维

#include <stdio.h>int a[2][2] = {{1,1},{1,0}};int* matrix_multi(int *A, int *B){int i, j, k;int *temp = new int[4];for(i = 0; i < 2; i ++){for (j = 0; j < 2; j++){    *(temp+2*i+j) = 0;    for (k = 0; k < 2; k ++){                                *(temp+2*i+j) = *(temp+2*i+j) + (*(A+2*i+k)) * (*(B+2*k+j));}}}return temp; }int* f(int n){if(n < 1) return NULL;if(n == 1) return *a;if(n%2 == 0){        return matrix_multi(f(n/2), f(n/2));    }    else {        return matrix_multi(matrix_multi(f((n-1)/2), f((n-1)/2)), *a);    }}int main(){printf("%d", *(f(10)+1));return 0;}

也可直接用二维矩阵

#include <stdio.h> int ** matrix_multi(int** A, int** B){    int ** p = new int *[2];    for (int i =0; i<2; i++){        p[i] = new int[2];    }    for (int i =0; i<2; i++){        for(int j = 0; j < 2; j++){            for (int k = 0; k < 2; k++){                p[i][j] = p[i][j] + A[i][k]*B[k][j];            }        }    }    return p;}int** f(int** a, int n){      if(n < 1) return NULL;      if(n == 1) return a;      if(n%2 == 0){          return matrix_multi(f(a, n/2), f(a, n/2));      }      else {          return matrix_multi(matrix_multi(f(a, (n-1)/2), f(a, (n-1)/2)), a);      }  }  int main(){     int ** a = new int *[2];    for (int i =0; i<2; i++){        a[i] = new int[2];    }    a[0][0] = 1;    a[0][1] = 1;    a[1][0] = 1;    a[1][1] = 0;    printf("%d", *(*f(a, 10)+1));      printf("\n");    return 0;  }  

上面计算出来的结果都是55,即fibo(10)的结果


计算矩阵的乘法

一般而言矩阵的乘法需要三层for循环,这是一个n^3的算法。strassen提出了一个n^(log2(7))的算法,教授也讲了,这个我就不说了。


VLSI (超大规模集成电路)的布局

其实就是求完全二叉树怎么放占的矩形面积最小,要求每个树的顶点在点阵的点上。

最后面积是O(n),长宽各sqrt(n),做一个H布局。

这个我觉得纯属智力题,老师也没说怎么推导出来的。





 





0 0
原创粉丝点击