Day-8 Divide-and-Conquer-3 : 二分搜寻法, 费波那契数列, Strassen’s演算法

二分搜寻法(Binary Search)

前提,在一个已经排序完成的A阵列中
Divide : 元素x和A阵列的中间元素进行比较
Conquer : 在其中一个子阵列中进行递回,如果x小於A阵列的中间元素,就在左子阵列进行递回。
Combine : 没有
递回关系式: https://chart.googleapis.com/chart?cht=tx&chl=T(n)%20%3D%20T(%5Cfrac%20n%202)%20%2B%20%5CTheta(1)
发现这个递回关系式符合关系式https://chart.googleapis.com/chart?cht=tx&chl=T(n)%3DaT(n%2Fb)%2Bf(n),可以使用主定理(master method)求解,代入https://chart.googleapis.com/chart?cht=tx&chl=n%5E%7B%5Clog_ba%7D,得到https://chart.googleapis.com/chart?cht=tx&chl=n%5E%7B%5Clog_21%7D,这个结果大於https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(1)(主方法case 2),因此该递回式的解为https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E%7Blog2%5E1%7Dlg%5E%7B%5Cvarepsilon%2B1%7Dn)
https://chart.googleapis.com/chart?cht=tx&chl=(%5Clog_21%20%3D%200),得到解https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(%7Blg%7Dn)

int binary_search(vector<int> &array, int min, int max, int target)
{
    int mid = 0;
    if (max > min)
    {
        mid = start + (end - start)/2
        if(array[mid] == target)
        {
            return array[mid];
        }
        if (target < array[mid])
        {
            return binary_search(array, min, mid - 1, target);
        }
        else if(target > array[mid])
        {
            return binary_search(array, mid + 1, max, target);
        }
    }
    return -1;
}

x的n次方

正常一般情况,要计算x的n次方,即为将x * x * x....进行n次,每一次的乘法需要一个常数时间c,总时间为cn,用https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n)进行表示。

我们引入分治法的想法,去解决这个问题,我们可以把这个问题进行分解
https://chart.googleapis.com/chart?cht=tx&chl=x%5En%20%3D%20x%5E%7B%5Cfrac%20n%202%7D%20%2B%20x%5E%7B%5Cfrac%20n%202%7D,当n为偶数时,如果我们得知https://chart.googleapis.com/chart?cht=tx&chl=x%5E%7B%5Cfrac%20n%202%7D的结果,只剩下https://chart.googleapis.com/chart?cht=tx&chl=x%5E%7B%5Cfrac%20n%202%7D%20%2B%20x%5E%7B%5Cfrac%20n%202%7D需要计算,这需要一个常数时间,因此,复杂度就会变成原来的一半,将这个递回关系写出
https://chart.googleapis.com/chart?cht=tx&chl=T(n)%20%3D%20T(%5Cfrac%20n%202)%20%2B%20%5CTheta(1),如同二分搜寻法的结果,得出的解为https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(%7Blg%7Dn),这个方法就称为native recursive squaring。

费波那契数列

定义
https://chart.googleapis.com/chart?cht=tx&chl=F(n)%20%3D%20%20%20%5Cbegin%7Barray%7D%20%5C%200%20%5C%20if%20%5C%20n%20%3D%200%20%2C%5C%5C%20%201%20%5C%20if%20%5C%20n%20%3D%201%2C%5C%5C%20F(n%20-%201)%20%2B%20F(n%20-%202)%2C%20%5C%20if%20%5C%20n%20%3E%3D2%2C%20%20%5Cend%7Barray%7D
时间复杂度 : https://chart.googleapis.com/chart?cht=tx&chl=%5COmega(%5Cphi%5En), https://chart.googleapis.com/chart?cht=tx&chl=%5Cphi为黄金比例常数,https://chart.googleapis.com/chart?cht=tx&chl=%5Cphi%20%3D%20%5Cfrac%20%7B1%2B%20%5Csqrt%205%7D%202
这是一个指数型的时间复杂度,而我们希望他可以式多项式的时间复杂度。

如果我们将费波那契数列的递回情况画成一棵树,会发现存在许多相同的子树,也就是有许多情况不断地重复发生,以至於浪费了许多时间,我们可以将计算过的结果放在一个阵列当中(动态规划的想法),此持我们在计算https://chart.googleapis.com/chart?cht=tx&chl=F(n)时间就会是https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n)(我们只需要知道前n项相加即可)。

native recursive squaring(朴素平方递回)的时间复杂度为https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(%7Blg%7Dn),如果我们将https://chart.googleapis.com/chart?cht=tx&chl=F(n)%20%3D%20%5Cphi%5En%20%2F%20%5Csqrt%205取整数到与其最接近的整数,我们会发现其结果就是第n位的费波那契数。

但是在真实的机器中,这个方法是不可行的,原因是因为我们必须使用浮点数表示https://chart.googleapis.com/chart?cht=tx&chl=%5Cphihttps://chart.googleapis.com/chart?cht=tx&chl=%5Csqrt%205,直接取整可能会有错误发生。

我们可以利用费波那契数的特性,来使用平方递回

https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20F_%7Bn%2B1%7D%26F_n%5C%5CF_n%26F_%7Bn-1%7D%20%5Cend%7Bpmatrix%7D%5Cquad%20%3D%5Cbegin%7Bpmatrix%7D%201%261%5C%5C1%260%20%5Cend%7Bpmatrix%7D%5En%5Cquad
2*2的矩阵和2*2的矩阵进行乘法运算得出来的结果依然是2*2的矩阵,规模不会变大,我们使用分治法的出的时间复杂度依然会保持在https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(%7Blg%7Dn)


下面使用数学归纳法进行证明:

  • 基本情况(base): https://chart.googleapis.com/chart?cht=tx&chl=%3D%5Cbegin%7Bpmatrix%7D%201%261%5C%5C1%260%20%5Cend%7Bpmatrix%7D%5E1%5Cquad%20%3D%20%5Cbegin%7Bpmatrix%7D%20F_2%26F_1%5C%5CF_1%26F_0%20%5Cend%7Bpmatrix%7D%5Cquad
  • 推递步骤(step): https://chart.googleapis.com/chart?cht=tx&chl=%3D%5Cbegin%7Bpmatrix%7D%20F_%7Bn%2B1%7D%26F_n%5C%5CF_n%26F_%7Bn-1%7D%20%5Cend%7Bpmatrix%7D%5Cquad%20%3D https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20F_%7Bn%7D%26F_%7Bn%20-%201%7D%5C%5CF_%7Bn-1%7D%26F_%7Bn-2%7D%20%5Cend%7Bpmatrix%7D%5Cquad https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%201%261%5C%5C1%260%20%5Cend%7Bpmatrix%7D%5Cquad%20%3D https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%201%261%5C%5C1%260%20%5Cend%7Bpmatrix%7D%5E%7Bn%20-%201%7D%5Cquad
    如果将n-1,就会具备从左式到右式这样的特性,根据归纳假设。
    我们可以试着将https://chart.googleapis.com/chart?cht=tx&chl=F_%7Bn%20%2B%201%7D结果展开,也就是求右式矩阵乘法展开的结果,会得到https://chart.googleapis.com/chart?cht=tx&chl=F_%7Bn%20%2B%201%7D%3DF_n%2BF%7Bn%20-%201%7D,恰好为费波那契数的定义,使假设正确。

我们将上面进行结合,得到
https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%201%261%5C%5C1%260%20%5Cend%7Bpmatrix%7D%5E%7Bn%20-%201%7D%5Cquad https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%201%261%5C%5C1%260%20%5Cend%7Bpmatrix%7D%5Cquad https://chart.googleapis.com/chart?cht=tx&chl=%3D%5Cbegin%7Bpmatrix%7D%201%261%5C%5C1%260%20%5Cend%7Bpmatrix%7D%5En%5Cquad

根据数学归纳法,证明完毕。

矩阵乘法(Matrix multiplication)

Input: https://chart.googleapis.com/chart?cht=tx&chl=A%20%3D%20%5Cbegin%7Bbmatrix%7D%20a_%7Bij%7D%5Cend%7Bbmatrix%7D, https://chart.googleapis.com/chart?cht=tx&chl=B%20%3D%20%5Cbegin%7Bbmatrix%7D%20b_%7Bij%7D%5Cend%7Bbmatrix%7D, https://chart.googleapis.com/chart?cht=tx&chl=i%2Cj%20%3D%201%2C2...%2Cn
Output: https://chart.googleapis.com/chart?cht=tx&chl=C%20%3D%20%5Cbegin%7Bbmatrix%7D%20c_%7Bij%7D%5Cend%7Bbmatrix%7D%20%3D%20A*B

https://chart.googleapis.com/chart?cht=tx&chl=C_%7Bij%7D%20%3D%5Csum_%7Bk%3D1%7D%5Ena_%7Bik%7D%5C%20%5C%20b_%7Bkj%7D
假设是一个2*2的矩阵乘法,矩阵C总共有4项需要计算,每一个项需要2步的计算
https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20a_%7B1%2C1%7D%26a_%7B1%2C2%7D%5C%5Ca_%7B2%2C1%7D%26a_%7B2%2C2%7D%20%5Cend%7Bpmatrix%7D https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20b_%7B1%2C1%7D%26b_%7B1%2C2%7D%5C%5Cb_%7B2%2C1%7D%26b_%7B2%2C2%7D%20%5Cend%7Bpmatrix%7D%20%3D https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20a_%7B1%2C1%7Db_%7B1%2C1%7D%20%2B%20a_%7B1%2C2%7Db_%7B2%2C1%7D%26%20a_%7B1%2C1%7Db_%7B1%2C2%7D%20%2B%20a_%7B1%2C2%7Db_%7B2%2C2%7D%5C%5C%20a_%7B2%2C1%7Db_%7B1%2C1%7D%20%2B%20a_%7B2%2C2%7Db_%7B2%2C2%7D%26%20a_%7B2%2C1%7Db_%7B1%2C2%7D%20%2B%20a_%7B2%2C2%7Db_%7B2%2C2%7D%20%5Cend%7Bpmatrix%7D

若有一个n*n的矩阵乘法。矩阵C需要https://chart.googleapis.com/chart?cht=tx&chl=n%5E2项需要计算,每一项需要n步,整体复杂度为https://chart.googleapis.com/chart?cht=tx&chl=n%5E3

int main(void)
{
    int A[2][2] = {{2, 3}, {4, 5}};
    int B[2][2] = {{6, 7}, {8, 9}};
    int C[2][2] = {{0, 0}, {0, 0}};

    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            for (int k = 0; k < 2; k++)
            {
                C[i][j] = C[i][j] + A[i][k] * B[k][j];
            }
        }
    }

我们使用分治法降低时间复杂度,每个矩阵有https://chart.googleapis.com/chart?cht=tx&chl=n%5E2数字(因为是方阵),上面分治法的想法都是将规模为n的问题分解成n/2的两个子问题,我们希望一个n*n的矩阵可以转化为两个n/2*n/2规模的矩阵,我们可以使用分块矩阵(block matrix)的想法


这个例子我们将4*4的矩阵看做4个2*2的矩阵,也就是将n*n的矩阵拆分成数个n/2*n/2的矩阵

https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20a%26b%5C%5Cc%26d%20%5Cend%7Bpmatrix%7D https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20e%26f%5C%5Cg%26h%20%5Cend%7Bpmatrix%7D https://chart.googleapis.com/chart?cht=tx&chl=%3D%20%5Cbegin%7Bpmatrix%7D%20r%26s%5C%5Ct%26u%20%5Cend%7Bpmatrix%7D
我们可以得到以下的关系
https://chart.googleapis.com/chart?cht=tx&chl=r%20%3D%20ae%2Bbg
https://chart.googleapis.com/chart?cht=tx&chl=s%20%3D%20af%2Bbh
https://chart.googleapis.com/chart?cht=tx&chl=t%20%3D%20ce%2Bdg
https://chart.googleapis.com/chart?cht=tx&chl=u%20%3D%20cf%2Bdh

https://chart.googleapis.com/chart?cht=tx&chl=a%2Cb%2Cc%2Cd我们将其视为A矩阵,https://chart.googleapis.com/chart?cht=tx&chl=e%2Cf%2Cg%2Ch将其视为B矩阵,https://chart.googleapis.com/chart?cht=tx&chl=r%2Cs%2Ct%2Cu将其视为C矩阵,我们要求的C矩阵的结果,就是将上面结果计算并组合之後就能够得到,每一个字母我们可以想像成是一个n/2规模的矩阵,我们需要不断的递回计算乘积,直到每一个字母是一个数字。

在上面这个例子中,我们可以得到我们需要进行8次的递回运算,也就是https://chart.googleapis.com/chart?cht=tx&chl=n%5E3,以及4次的求和运算,将两个矩阵相加所需要的时间复杂度是https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E2),矩阵加法的复杂度已经无法再减少了,目前矩阵乘法的总复杂度为:

https://chart.googleapis.com/chart?cht=tx&chl=T(n)%20%3D%208T(n%2F2)%20%2B%20%5CTheta(n%5E2),发现这个递回关系式符合关系式https://chart.googleapis.com/chart?cht=tx&chl=T(n)%3DaT(n%2Fb)%2Bf(n),因此使用主定理来解出这个递回式
代入https://chart.googleapis.com/chart?cht=tx&chl=n%5E%7B%5Clog_ba%7D得到https://chart.googleapis.com/chart?cht=tx&chl=n%5E3https://chart.googleapis.com/chart?cht=tx&chl=n%5E3%3E%5CTheta(n%5E2),属於case 1.
因此结果为https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E%7B%5Clog_ba%7D),也就是https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E3)

Strassen's algorithm

这个想法的思路为,我们必须绕过这些乘法,也就是减少乘法发生的次数,上面这个例子中,我们需要进行8次,需要进行8次乘法运算并加总才能够得到C,我们尝试减少乘法发生的次数,以加快矩阵乘法的速度。在这个演算法中,我们可以让乘法发生的次数降低到7次。

https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20a%26b%5C%5Cc%26d%20%5Cend%7Bpmatrix%7D https://chart.googleapis.com/chart?cht=tx&chl=%5Cbegin%7Bpmatrix%7D%20e%26f%5C%5Cg%26h%20%5Cend%7Bpmatrix%7D https://chart.googleapis.com/chart?cht=tx&chl=%3D%5Cbegin%7Bpmatrix%7D%20r%26s%5C%5Ct%26u%20%5Cend%7Bpmatrix%7D

https://chart.googleapis.com/chart?cht=tx&chl=P_1%20%3D%20a(f-h)
https://chart.googleapis.com/chart?cht=tx&chl=P_2%20%3D%20(a%2Bb)h
https://chart.googleapis.com/chart?cht=tx&chl=P_3%20%3D%20(c%2Bd)e
https://chart.googleapis.com/chart?cht=tx&chl=P_4%20%3D%20d(g-e)
https://chart.googleapis.com/chart?cht=tx&chl=P_5%20%3D%20(a%2Bd)(e%2Bh)
https://chart.googleapis.com/chart?cht=tx&chl=P_6%20%3D%20(b-d)(g%2Bh)
https://chart.googleapis.com/chart?cht=tx&chl=P_7%20%3D%20(a-c)(e%2Bf)

我们可以在https://chart.googleapis.com/chart?cht=tx&chl=7T(n%2F2)的时间完成这一些操作,之後我们将https://chart.googleapis.com/chart?cht=tx&chl=r%2Cs%2Ct%2Cu这四项元素用上面的符号进行表示:
https://chart.googleapis.com/chart?cht=tx&chl=r%20%3D%20P_5%20%2B%20P_4%20-%20P_2%20%2B%20P_6
https://chart.googleapis.com/chart?cht=tx&chl=s%20%3D%20P_1%20%2B%20P_2
https://chart.googleapis.com/chart?cht=tx&chl=t%20%3D%20P_3%20%2B%20P_4
https://chart.googleapis.com/chart?cht=tx&chl=u%20%3D%20P_5%20%2B%20P_1%20-%20P_3%20-%20P_7

我们可以确认看看这一些结果,以u作为例子
https://chart.googleapis.com/chart?cht=tx&chl=u%20%3D%20P_5%20%2B%20P_1%20-%20P_3%20-%20P_7%20%3D%20(ae%2Bah%2Bde%2Bdh)%20%2B%20(af-ah)%20-%20(ce%2Bde)%20-%20(ae%2Baf-ce-cf)
得到https://chart.googleapis.com/chart?cht=tx&chl=u%20%3D%20cf%2Bdh,会发现跟我们上方正常进行矩阵乘法运算的结果是相同的

这个演算法使用分治法的思路如下
Divide : 将A矩阵和B矩阵进行拆分,然後求得一些乘积项,也就是计算出所有的P,这一步需要https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E2)

Conquer : 递回的方式处理每一个https://chart.googleapis.com/chart?cht=tx&chl=P_i,得到7个乘积

Combine : 结合每一项P的结果,得出https://chart.googleapis.com/chart?cht=tx&chl=r%2Cs%2Ct%2Cu,需要https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E2)

整个演算法的递回关系式为https://chart.googleapis.com/chart?cht=tx&chl=T(n)%20%3D%207T(n%2F2)%20%2B%20%5CTheta(n%5E2),发现这个递回关系式符合关系式https://chart.googleapis.com/chart?cht=tx&chl=T(n)%3DaT(n%2Fb)%2Bf(n),因此使用主定理来解出这个递回式
代入https://chart.googleapis.com/chart?cht=tx&chl=n%5E%7B%5Clog_b%5Ea%7D得到https://chart.googleapis.com/chart?cht=tx&chl=n%5E%7B%5C%20log2%5E7%7D,https://chart.googleapis.com/chart?cht=tx&chl=%5Clog_27%20%3D%202.807,属於case 1.因此时间复杂度为https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E%7B2.807%7D),比https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E3)好,但不是最好的演算法,目前最好的矩阵乘法演算法为Coppersmith-Winograd,复杂度为https://chart.googleapis.com/chart?cht=tx&chl=%5CTheta(n%5E%7B2.376%7D)

参考资料: Introduction to algorithms 3rd


<<:  【Day19】 用 4 种不同的 GAN 模型生成音乐简介

>>:  初学者跪着学JavaScript Day4 : 宣告:let、const

【Day 12】 实作 - 透过 AWS 服务 - Athena 建立以及查询资料表

大家午安~ 在第 8、9 天我们完成 Data Collection 以及 Google Analy...

【Debug】PHP Key-Value 输入格式错误

Key-value 版本兼容警告 如果key不加引号 $data=[ john1=>1.2, ...

Day 12 Generics Part 2

今天要介绍的是 generic classes 上面可以看到出现了很多错误,因为 data、item...

Android学习笔记18

今天把dialogfragment也搭配上bindnig然後试着把dialog的动作移到dialog...

网页常用单位-30天学会HTML+CSS,制作精美网站

设置CSS样式大小时,会使用到各种不同的单位,尤其现在都制作响应式网站,用错单位,就会针对不同尺寸调...