karatsuba大数乘法问题及其高效算法

2023-11-10

转载自: iTimeTraveler博客

题目

编写两个任意位数的大数相乘的程序,给出计算结果。比如:

题目描述: 输出两个不超过100位的大整数的乘积。
输入: 输入两个大整数,如1234567 和 123
输出: 输出乘积,如:151851741

或者

求 1234567891011121314151617181920 * 2019181716151413121110987654321 的乘积结果

分析

所谓大数相乘(Multiplication algorithm),就是指数字比较大,相乘的结果超出了基本类型的表示范围,所以这样的数不能够直接做乘法运算。

参考了很多资料,包括维基百科词条Multiplication algorithm,才知道目前大数乘法算法主要有以下几种思路:

  1. 模拟小学乘法:最简单的乘法竖式手算的累加型;
  2. 分治乘法:最简单的是Karatsuba乘法,一般化以后有Toom-Cook乘法;
  3. 快速傅里叶变换FFT:(为了避免精度问题,可以改用快速数论变换FNTT),时间复杂度O(N lgN lglgN)。具体可参照Schönhage–Strassen algorithm
  4. 中国剩余定理:把每个数分解到一些互素的模上,然后每个同余方程对应乘起来就行;
  5. Furer’s algorithm:在渐进意义上FNTT还快的算法。不过好像不太实用,本文就不作介绍了。大家可以参考维基百科Fürer’s algorithm

解法

我们分别实现一下以上算法,既然不能直接使用乘法做运算,最简单最容易想到的办法就是模拟乘法运算。

1、模拟乘法手算累加

      7 8 9 6 5 2
×         3 2 1 1
-----------------
      7 8 9 6 5 2   <---- 第1趟 
    7 8 9 6 5 2     <---- 第2趟 
   ..........       <---- 第n趟 
-----------------
  ? ? ? ? ? ? ? ?   <---- 最后的值用另一个数组表示

如上所示,乘法运算可以分拆为两步:

  • 第一步,是将乘数与被乘数逐位相乘;
  • 第二步,将逐位相乘得到的结果,对应相加起来。

这有点类似小学数学中,计算乘法时通常采用的“竖式运算”。用Java简单实现了这个算法,代码如下:

/**
 * 大数相乘 - 模拟乘法手算累加
 */
public static Integer[] bigNumberMultiply(int[] arr1, int[] arr2){
    ArrayList<Integer> result = new ArrayList<>();  //中间求和的结果

    //arr2 逐位与arr1相乘
    for(int i = arr2.length - 1; i >= 0; i--){
        int carry = 0;
        ArrayList<Integer> singleList = new ArrayList<>();

        //arr2 逐位单次乘法的结果
        for(int j = arr1.length - 1; j >= 0; j--){
            int r = arr2[i] * arr1[j] + carry;
            int digit = r % 10;
            carry = r / 10;

            singleList.add(digit);
        }
        if(carry != 0){
            singleList.add(carry);
        }

        int resultCarry = 0, count = 0;
        int k = 0;
        int l = 0;
        int offset = arr2.length - 1 - i;       //加法的偏移位
        ArrayList<Integer> middleResult = new ArrayList<>();

        //arr2每位乘法的结果与上一轮的求和结果相加,从右向左做加法并进位
        while (k < singleList.size() || l < result.size()) {
            int kv = 0, lv = 0;
            if (k < singleList.size() && count >= offset) {
                kv = singleList.get(k++);
            }
            if (l < result.size()) {
                lv = result.get(l++);
            }
            int sum = resultCarry + kv + lv;
            middleResult.add(sum % 10);     //相加结果从右向左(高位到低位)暂时存储,最后需要逆向输出
            resultCarry = sum / 10;
            count++;
        }
        if(resultCarry != 0){
            middleResult.add(resultCarry);
        }
        result.clear();
        result = middleResult;
    }

    Collections.reverse(result);    //逆向输出结果
    return result.toArray(new Integer[result.size()]);
}

看了以上的代码,感觉思路虽然很简单,但是实现起来却很麻烦,那么我们有没有别的方法来实现这个程序呢?答案是有的,接下来我来介绍第二种方法。

2、模拟乘法累加 - 改进

简单来说,方法二就是先不算任何的进位,也就是说,将每一位相乘,相加的结果保存到同一个位置,到最后才计算进位

例如:计算98×21,步骤如下

        9  8
×       2  1
-------------
       (9)(8)  <---- 第1趟: 98×1的每一位结果 
  (18)(16)     <---- 第2趟: 98×2的每一位结果 
-------------
  (18)(25)(8)  <---- 这里就是相对位的和,还没有累加进位

这里唯一要注意的便是进位问题,我们可以先不考虑进位,当所有位对应相加,产生结果之后,再考虑。从右向左依次累加,如果该位的数字大于10,那么我们用取余运算,在该位上只保留取余后的个位数,而将十位数进位(通过模运算得到)累加到高位便可,循环直到累加完毕。

核心代码如下:

/**
 * 大数相乘方法二
 */
public static int[] bigNumberMultiply2(int[] num1, int[] num2){
    // 分配一个空间,用来存储运算的结果,num1长的数 * num2长的数,结果不会超过num1+num2长
    int[] result = new int[num1.length + num2.length];

    // 先不考虑进位问题,根据竖式的乘法运算,num1的第i位与num2的第j位相乘,结果应该存放在结果的第i+j位上
    for (int i = 0; i < num1.length; i++){
        for (int j = 0; j < num2.length; j++){
            result[i + j + 1] += num1[i] * num2[j];	 // (因为进位的问题,最终放置到第i+j+1位)
        }
    }

    //单独处理进位
    for(int k = result.length-1; k > 0; k--){
        if(result[k] > 10){
            result[k - 1] += result[k] / 10;
            result[k] %= 10;
        }
    }
    return result;
}

!!注意:这里的进位有个大坑,因为result[]数组是从左到右记录相对位的和(还没有进位),而最后的进位是从右向左累加进位,这样的话,如果最高位,也就是最左侧那一位的累加结果需要进位的话,result[]数组就没有空间存放了。

而正好result[]数组的最后一位空置,不可能被占用,我们就响应地把num1的第i位与num2的第j位相乘,结果应该存放在结果的第i+j位上的这个结果往后顺移一位放到第i+j+1位,最后从右向左累加时就多了一个空间。

3、分治 - Karatsuba算法

以上两种模拟乘法的手算累加型算法,他们都是模拟普通乘法的计算方式,时间复杂度都是O(n^{^{2}}),而这个Karatsuba算法,时间复杂度仅有 O(n^{log_23}) 。下面,我就来介绍一下这个算法。

Karatsuba于1960年发明在 O(n^{log_23}) 步骤内将两个n位数相乘的Karatsuba算法。它反证了安德雷·柯尔莫哥洛夫于1956年认为这个乘法需要 Ω(n^{^{2}}) 步骤的猜想。

首先来看看这个算法是怎么进行计算的,见下图:

图中显示了计算5678 * 1234的过程,首先是拆分成abcd四个部分,然后分别计算acbd(a+b)*(c+d),最后再用第三个算式的结果减去前面两个(其实得到的就是bc+ad,但是减少了乘法步骤),然后,计算式1后面加4个0,计算式2后面不加,计算式3后面加2个0,再把这三者相加,就是正确结果。

接下来,就来证明一下这个算法的正确性。这是一幅来自Karatsuba Multiplication Algorithm – Python Code的图,我们来看看:

我们假设要相乘的两个数是x * y。我们可以把x,y写成:

x=a∗10^{n/2}+bx=a∗110^{n/2}+b

y=c∗10^{n/2}+dy=c∗10^{n/2}+d

这里的n是数字的位数。如果是偶数,则a和b都是n/2位的。如果n是奇数,则你可以让a是n/2+1位,b是n/2位。(例如a = 12,b = 34;a = 123,b = 45),那么x*y就可以换算为:

   x∗y

=(a∗10^{n/2}+b)∗(c∗10^{n/2}+d)

=ac∗10^{n}+(ad+bc)∗10^{n/2}+bd

=ac∗10^{n}+[(a+b)∗(c+d)−ac−bd]∗10^{n/2}+bd

注意最后一步,这个式子倒数第二步中的(ad + bc),没必要另外进行两次乘法,可以使用((a+b)*(c+d) - ac - bd)来重复利用前面的两次乘积结果ac 和 bd

也就是说,我们发现最终只需要计算三次乘法 a*c, b*d, (a+b)*(c+d) 以及六次加法。因此这样复杂度就变为

T(n)=3T(n/2)+6n=O(nlog_23)

对比之前的计算过程,结果已经呼之欲出了。这里唯一需要注意的两点就是:

  1. (a*d + b*c)的计算为了防止两次乘法,应该使用之前的计算也就是第一幅图第四步的③-②-①
  2. 这些乘法在算法里应该是递归实现的,数字很大时,先拆分,然后拆分出来的数字还是很大的话,就继续拆分,直到a * b已经是一个非常简单的小问题为之。这也是分治的思想。

我们举例来尝试一下这种算法,比如计算12345 * 6789,我们让a = 12b = 345。同时c = 6d = 789。也就是:

12345=12⋅1000+345

6789=6⋅1000+789

那么a*cb*d的结果如下:

z2=a∗c=12×6=72

z0=b∗d=345×789=272205

z1=((a+b)∗(c+d)−a∗c−b∗d)

    =(12+345)×(6+789)−z2−z0=283815−72−272205=11538

最终结果就是:

result=z2⋅10^{^{2*3}}+z1⋅10^{^{3}}+z0

result=72⋅10^{^{6}}+11538⋅10^{^{3}}+272205=83810205

以上就是使用分治的方式计算乘法的原理。上面这个算法,由 Anatolii Alexeevitch Karatsuba 于1960年提出并于1962年发表,所以也被称为 Karatsuba 乘法。

根据上面的思路,实现的Karatsuba乘法代码如下:

/**
 * Karatsuba乘法
 */
public static long karatsuba(long num1, long num2){
    //递归终止条件
    if(num1 < 10 || num2 < 10) return num1 * num2;

    // 计算拆分长度
    int size1 = String.valueOf(num1).length();
    int size2 = String.valueOf(num2).length();
    int halfN = Math.max(size1, size2) / 2;

    /* 拆分为a, b, c, d */
    long a = Long.valueOf(String.valueOf(num1).substring(0, size1 - halfN));
    long b = Long.valueOf(String.valueOf(num1).substring(size1 - halfN));
    long c = Long.valueOf(String.valueOf(num2).substring(0, size2 - halfN));
    long d = Long.valueOf(String.valueOf(num2).substring(size2 - halfN));

    // 计算z2, z0, z1, 此处的乘法使用递归
    long z2 = karatsuba(a, c);
    long z0 = karatsuba(b, d);
    long z1 = karatsuba((a + b), (c + d)) - z0 - z2;

    return (long)(z2 * Math.pow(10, (2*halfN)) + z1 * Math.pow(10, halfN) + z0);
}

总结:

Karatsuba 算法是比较简单的递归乘法,把输入拆分成 2 部分,不过对于更大的数,可以把输入拆分成 3 部分甚至 4 部分。拆分为 3 部分时,可以使用下面的Toom-Cook 3-way 乘法,复杂度降低到 O(n^1.465)。拆分为 4 部分时,使用Toom-Cook 4-way 乘法,复杂度进一步下降到 O(n^1.404)。对于更大的数字,可以拆成 100 段,使用快速傅里叶变换FFT,复杂度接近线性,大约是 O(n^1.149)。可以看出,分割越大,时间复杂度就越低,但是所要计算的中间项以及合并最终结果的过程就会越复杂,开销会增加,因此分割点上升,对于公钥加密,暂时用不到太大的整数,所以使用 Karatsuba 就合适了,不用再去弄更复杂的递归乘法。

测试程序

public class LeetcodeTest {

    public static void main(String[] args) {
//        String a = "1234567891011121314151617181920";
//        String b = "2019181716151413121110987654321";

//        String a = "999999999999";
//        String b = "999999999999";

//        String a = "24566";
//        String b = "452053";

        String a = "98";
        String b = "21";

        char[] charArr1 = a.trim().toCharArray();
        char[] charArr2 = b.trim().toCharArray();

        // 字符数组转换为int[]数组
        int[] arr1 = new int[charArr1.length];
        int[] arr2 = new int[charArr2.length];
        for(int i = 0; i < charArr1.length; i++){
            arr1[i] = charArr1[i] - '0';
        }
        for(int i = 0; i < charArr2.length; i++){
            arr2[i] = charArr2[i] - '0';
        }

        // 开始计算
        int[] result = LeetcodeTest.bigNumberMultiply2(arr1, arr2);
        System.out.println(a + " * " + b + " = " + Arrays.toString(result).replace(", ", ""));
    }
}

最后,是测试用例输出结果:

1234567891011121314151617181920 * 2019181716151413121110987654321 =[02492816912877266687794240983772975935013386905490061131076320]

999999999999 * 999999999999 = [999999999998000000000001]

24566 * 452053 = [11105133998]

98 * 21 = [2058]

 

Java中BigInteger的乘法实现

目前最著名的高精度整数运算库是GNU的GMP,GMP是The GNU MP Bignum Library,是一个开源的数学运算库,它可以用于任意精度的数学运算,包括有符号整数、有理数和浮点数。它本身并没有精度限制,只取决于机器的硬件情况。许多著名的计算机代数系统如Axiom, Maple, Mathematica, Maxima等的底层高精度整数运算都是基于GMP实现的。

设n 为乘数的位数, 就目前已知的情况而言, 不同乘法算法的时间复杂度可以从平凡的O(n^{^{2}})(普通乘法), O(n^{log_23})(Karatsuba 乘法), O(n^{log_35})(Toom-3 乘法),O(nlog^{*}n)(复数域上的FFT),其中

log^{*}n=logn(loglogn)(logloglogn)⋅⋅⋅,

 和O(n(logn)(loglogn))(有限域上的FFT), 其中“有限域上的FFT”的时间复杂度已经相当接近线性了。

但是这些乘法算法中复杂度较低的算法往往有较大的常数因子, 因此如果乘数的位数较少,普通乘法反而是最快的, 所以实用中常常将这些不同的乘法算法结合起来使用, 每次做乘法时都根据相乘两数的大小动态地选择具体采用哪一种算法, 而每种算法的最佳适用范围往往依赖于具体实现和硬件环境, 因此一般直接通过实验来确定。

Java中的实现

  1. 在 Java 7 里面,就是用二重循环直接乘的。源代码:[BigInteger - Java7] (见1165行)

  2. 在 Java 8 里面,根据两个因数的大小,有三种乘法。源代码:[BigInteger - Java8] (见1464行):

    • 当两个因数均小于 2^{^{32*80}}时,用二重循环直接相乘,复杂度为O(n^{^{2}}),n为因数位数(下同);
    • 否则,当两个因数均小于2^{^{32*240}}时,采用 Karatsuba algorithm,其复杂度为O(n^{log_23})≈O(n^{^{1.585}})
    • 否则,采用 Toom-Cook multiplication,其复杂度为O(n^{log_35})≈O(n^{^{1.465}})

 其中,Java8中的源代码如下:

private static final int MULTIPLY_SQUARE_THRESHOLD = 20;

private static final int KARATSUBA_THRESHOLD = 80;

private static final int TOOM_COOK_THRESHOLD = 240;

public BigInteger multiply(BigInteger val) {
    if (val.signum == 0 || signum == 0)
        return ZERO;

    int xlen = mag.length;

    if (val == this && xlen > MULTIPLY_SQUARE_THRESHOLD) {
        return square();
    }

    int ylen = val.mag.length;

    if ((xlen < KARATSUBA_THRESHOLD) || (ylen < KARATSUBA_THRESHOLD)) {
        int resultSign = signum == val.signum ? 1 : -1;
        if (val.mag.length == 1) {
            return multiplyByInt(mag,val.mag[0], resultSign);
        }
        if (mag.length == 1) {
            return multiplyByInt(val.mag,mag[0], resultSign);
        }
        int[] result = multiplyToLen(mag, xlen,
                                     val.mag, ylen, null);
        result = trustedStripLeadingZeroInts(result);
        return new BigInteger(result, resultSign);
    } else {
        if ((xlen < TOOM_COOK_THRESHOLD) && (ylen < TOOM_COOK_THRESHOLD)) {
            // 采用 Karatsuba algorithm 算法
            return multiplyKaratsuba(this, val);
        } else {
            // 采用 Toom-Cook multiplication 3路乘法
            return multiplyToomCook3(this, val);
        }
    }
}

我们可以看到,Java8依据两个因数的量级分别使用Karatsuba algorithm 和 Toom-Cook multiplication 算法计算大数乘积。

Karatsuba algorithm

/**
 * Java8中的 Karatsuba algorithm 算法
 */
private static BigInteger multiplyKaratsuba(BigInteger x, BigInteger y) {
    int xlen = x.mag.length;
    int ylen = y.mag.length;

    // The number of ints in each half of the number.
    int half = (Math.max(xlen, ylen)+1) / 2;

    // xl and yl are the lower halves of x and y respectively,
    // xh and yh are the upper halves.
    BigInteger xl = x.getLower(half);
    BigInteger xh = x.getUpper(half);
    BigInteger yl = y.getLower(half);
    BigInteger yh = y.getUpper(half);

    BigInteger p1 = xh.multiply(yh);  // p1 = xh*yh
    BigInteger p2 = xl.multiply(yl);  // p2 = xl*yl

    // p3=(xh+xl)*(yh+yl)
    BigInteger p3 = xh.add(xl).multiply(yh.add(yl));

    // result = p1 * 2^(32*2*half) + (p3 - p1 - p2) * 2^(32*half) + p2
    BigInteger result = p1.shiftLeft(32*half).add(p3.subtract(p1).subtract(p2)).shiftLeft(32*half).add(p2);

    if (x.signum != y.signum) {
        return result.negate();
    } else {
        return result;
    }
}

Toom-Cook multiplication

/**
 * Java8中的 Toom-Cook multiplication 3路乘法
 */
private static BigInteger multiplyToomCook3(BigInteger a, BigInteger b) {
    int alen = a.mag.length;
    int blen = b.mag.length;

    int largest = Math.max(alen, blen);

    // k is the size (in ints) of the lower-order slices.
    int k = (largest+2)/3;   // Equal to ceil(largest/3)

    // r is the size (in ints) of the highest-order slice.
    int r = largest - 2*k;

    // Obtain slices of the numbers. a2 and b2 are the most significant
    // bits of the numbers a and b, and a0 and b0 the least significant.
    BigInteger a0, a1, a2, b0, b1, b2;
    a2 = a.getToomSlice(k, r, 0, largest);
    a1 = a.getToomSlice(k, r, 1, largest);
    a0 = a.getToomSlice(k, r, 2, largest);
    b2 = b.getToomSlice(k, r, 0, largest);
    b1 = b.getToomSlice(k, r, 1, largest);
    b0 = b.getToomSlice(k, r, 2, largest);

    BigInteger v0, v1, v2, vm1, vinf, t1, t2, tm1, da1, db1;

    v0 = a0.multiply(b0);
    da1 = a2.add(a0);
    db1 = b2.add(b0);
    vm1 = da1.subtract(a1).multiply(db1.subtract(b1));
    da1 = da1.add(a1);
    db1 = db1.add(b1);
    v1 = da1.multiply(db1);
    v2 = da1.add(a2).shiftLeft(1).subtract(a0).multiply(
         db1.add(b2).shiftLeft(1).subtract(b0));
    vinf = a2.multiply(b2);

    // The algorithm requires two divisions by 2 and one by 3.
    // All divisions are known to be exact, that is, they do not produce
    // remainders, and all results are positive.  The divisions by 2 are
    // implemented as right shifts which are relatively efficient, leaving
    // only an exact division by 3, which is done by a specialized
    // linear-time algorithm.
    t2 = v2.subtract(vm1).exactDivideBy3();
    tm1 = v1.subtract(vm1).shiftRight(1);
    t1 = v1.subtract(v0);
    t2 = t2.subtract(t1).shiftRight(1);
    t1 = t1.subtract(tm1).subtract(vinf);
    t2 = t2.subtract(vinf.shiftLeft(1));
    tm1 = tm1.subtract(t2);

    // Number of bits to shift left.
    int ss = k*32;

    BigInteger result = vinf.shiftLeft(ss).add(t2).shiftLeft(ss).add(t1).shiftLeft(ss).add(tm1).shiftLeft(ss).add(v0);

    if (a.signum != b.signum) {
        return result.negate();
    } else {
        return result;
    }
}

 

参考资料

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

karatsuba大数乘法问题及其高效算法 的相关文章

  • 50个c/c++源代码网站

    C C 是最主要的编程语言 这里列出了50名优秀网站和网页清单 这些网站提供c c 源代码 这份清单提供了源代码的链接以及它们的小说明 我已尽力包括最佳的C C 源代码的网站 这不是一个完整的清单 您有建议可以联系我 我将欢迎您的建 议 以
  • Mysql 数据库

    数据库基础 1 什么是数据库 用来存储数据 数据库可在硬盘及内存中存储数据 数据库与文件存储数据的区别 数据库本质也是通过文件来存储数据 数据库的概念就是系统的管理存储数据的文件 数据库介绍 本质就是存储数据的C S架构的socket套接字
  • netty handler的执行顺序(3)

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 今天解决2个问题 1 handler在pipeline当中究竟是如何存储的 2 在遍历handler的过程中 会根据event的不同 调用不同的handler 这一点是如何
  • 直线检测方法—LSD论文翻译

    附原文链接 LSD a Line Segment Detector 摘 要 LSD是一个线段检测器 能够在线性时间内得到亚像素级精度的检测结果 它无需调试参数就可以适用于任何数字图像上 并且能够自我控制错误数量的检测 平均来说 一个图像中允
  • 4399 C++笔试题

    1 写出一个函数 取到链表中倒数第二个节点 双链表 node getSec List mylist return mylist m tail gt m prev m prev为链表前指针 单链表 node getSec List mylis
  • Qt——用于表格QTableView的模型

    如果想使用表格来呈现数据 Qt提供了一个方便的部件QTableWidget 但是直接用它实现一些功能可能比较困难 这里将介绍一种强大 灵活的方式来操作表格 一 模型 视图架构 在这个架构中 模型用于存储数据 视图用于呈现数据 除此之外 还有
  • 循环单链表(C语言版)

    前言 小可爱们 本次一起来看看循环单链表吧 嘻嘻 一 循环单链表的定义 循环单链表是单链表的另一种形式 其结构特点链表中最后一个结点的指针域不再是结束标记 而是指向整个链表的第一个结点 从而使链表形成一个环 和单链表相同 循环链表也有带头结
  • Hash映射理解

    先说数组 数组优点之一 能通过索引很快定位到值 hashmap 就是利用了数组这个优点 对比 线性映射 定义一个数组 数组的元素是结构体 结构体包括 一对键 值 伪代码表示 a 0 struct Bill 5 a 1 struct KK 6
  • 数据结构小白之插入排序算法

    1 插入排序 1 1 思路 将n个需要排序的元素看成两个部分 一个是有序部分 一个是无序部分 开始的时候有序表只有一个元素 无序表有n 1个元素 排序过程中每次从无序表中取出元素 然后插入到有序表的适当位置 从而成为新的有序表 类似排队 如
  • UE4命令行使用,解释

    命令行在外部 从命令行运行编辑项目 1 导航到您的 LauncherInstall VersionNumber Engine Binaries Win64 目录中 2 右键单击上 UE4Editor exe 的可执行文件 并选择创建快捷方式
  • 链表面试题(一):反转链表的算法实现

    关于链表的考察 链表是面试里面经常涉及到的考点 因为链表的结构相比于Hashmap Hashtable Concurrenthashmap或者图等数据结构简单许多 对于后者更多面试的侧重点在于其底层实现 比如Hashmap中Entry
  • 浮生六记

    浮生六记 目录 浮生六记卷一 闺房记乐 002 浮生六记卷二 闲情记趣 015 浮生六记卷三 坎坷记愁 022 浮生六记卷四 浪游记快 034 浮生六记 2 浮生六记卷一 闺房记乐 余生乾隆癸未冬十一月二十有二日 正值太平盛世 且在 衣冠之
  • 算法问题实战策略

    算法问题实战策略 基本信息作者 韩 具宗万 译者 崔盛一出版社 人民邮电出版社ISBN 9787115384621上架时间 2015 2 4出版日期 2015 年3月开本 16开页码 738版次 1 1 内容简介 算法问题实战策略 本书收录
  • 数理统计知识整理——回归分析与方差分析

    题记 时值我的北科研究生第一年下 选学 统计优化 课程 备考促学 成此笔记 以谨记 1 线性回归 1 1 原理分析 要研究最大积雪深度x与灌溉面积y之间的关系 测试得到近10年的数据如下表 使用线性回归的方法可以估计x与y之间的线性关系 线
  • 时间复杂度+常见复杂度解释

    前言 算法的效率 虽然计算机能快速的完成运算处理 但实际上 它也需要根据输入数据的大小和算法效率来消耗一定的处理器资源 要想编写出能高效运行的程序 我们就需要考虑到算法的效率 算法的效率主要由以下两个复杂度来评估 时间复杂度 评估执行程序所
  • 基数排序代码实现

    详情请看排序总结 传送门 https blog csdn net m0 52711790 article details 121914543 基数排序的知识点我就不贴出来 相信都能搜到对应概念解释 下面就直接上代码 代码解释其实也很清晰了
  • 雪糕的最大数量 排序+贪心

    雪糕的最大数量 雪糕的最大数量 题目描述 样例 数据范围 思路 代码 题目描述 夏日炎炎 小男孩 Tony 想买一些雪糕消消暑 商店中新到 n 支雪糕 用长度为 n 的数组 costs 表示雪糕的定价 其中 costs i 表示第 i 支雪
  • Leetcode1094. 拼车

    Every day a Leetcode 题目来源 1094 拼车 解法1 差分数组 对于本题 设 a i 表示车行驶到位置 i 时车上的人数 我们需要判断是否所有 a i 都不超过 capacity trips i 相当于把 a 中下标从
  • 浅谈归并排序:合并 K 个升序链表的归并解法

    在面试中遇到了这道题 如何实现多个升序链表的合并 这是 LeetCode 上的一道原题 题目具体如下 用归并实现合并 K 个升序链表 LeetCode 23 合并K个升序链表 给你一个链表数组 每个链表都已经按升序排列 请你将所有链表合并到
  • 排序:计数排序

    一 概念 计数排序是非比较排序 是对哈希直接定址法的变形应用 二 思想 利用数组统计相同数据出现的次数 例如整型数据m出现n次 就在数组m位置记录数据为n 最后从头遍历数组打印数据即可 通俗来讲就是 数组下标即为数据 下标所指位置的值即为数

随机推荐

  • 【面试系列】JDK动态代理和CGLIB静态代理

    文章目录 前言 JDK动态代理代码实例 Cglib 代理代码实例 两者优缺点 前言 是否在面试过程中经常被问到Spring的代理的问题 比如说几种代理方式 两种代理方式的区别 或者问为什么JDK动态代理只能代理接口 如果你能回答出来JDK动
  • App内测神器之蒲公英--类似 testFlight fir.im

    一 前言部分 没发现蒲公英之前一直采用非常傻B的方式给公司App做内部测试 要么发个测试包让公司测试人员用iTUnes 自己安装 要么苦逼的一个个在我Xcode上直接安装测试包 操作起来又麻烦又苦逼 后来偶然发现了蒲公英感觉这货还真不是一般
  • 《TCP/IP详解卷一:协议》学习笔记八

    一 Traceroute程序的操作 1 Traceroute程序可以让我们看到IP数据报从一台主机传到另一台主机所经过的路由 其还可以让我们使用IP源路由选项 2 为什么不使用IP记录路由选项 RR 而另外开发一个新的应用程序 1 原先并不
  • 机器学习——KNN算法(K最近邻分类算法)(2020最新版)

    1 KNN的例子 转换为坐标 红色是爱情片 蓝色是动作片 黑色是需要判断的点 1 1 KNN具体的做法 其中 欧式距离 欧几里得距离 的计算方法 2 KNN的缺点 3 KNN的实现 coding utf 8 FileName knn alg
  • 静态联编与动态联编

    联编是指一个程序模块 代码之间相互关联的过程 静态联编 是程序的匹配 链接在编译阶段实现 也称早期匹配 重载函数就使用静态联编 编译的阶段 动态联编是指程序联编推迟到运行时候进行 又称晚期匹配 switch if语句就是动态联编的例子 执行
  • JVM调优几款好用的内存分析工具

    对于高并发访问量的电商 物联网 金融 社交等系统来说 JVM内存优化是非常有必要的 可以提高系统的吞吐量和性能 通常调优的首选方式是减少FGC次数或者FGC时间 以避免系统过多地暂停 FGC达到理想值后 比如一天或者两天触发一次FGC FC
  • (转载)jquery checkbox 设置选中和不选中

    https blog csdn net hantanxin article details 103187996 1 设置选中 hasApply prop checked true 设置不选中 hasApply prop checked fa
  • 使用openCV比对任意两张图片的相似度(亲测较准确)

    方案 使用openCV中的直方图算法做对比 测试效果较好 步骤 在java中使用openCV 1 引入openCV的依赖
  • openwrt 没有wifi

    wifi radio0 is disabled radio0 is disabled uci set wireless radio0 disabled 0
  • Web中什么是token,token的组成部分详解(jwt Token)

    token是计算机术语 令牌 令牌是一种能够控制站点占有媒体的特殊帧 以区别数据帧及其他控制帧 token其实说的更通俗点可以叫暗号 在一些数据传输之前 要先进行暗号的核对 不同的暗号被授权不同的数据操作 使用基于 Token 的身份验证方
  • K8s基础9——服务发现Coredns、Ingress Controller多种暴露方式、TLS+DaemonSet、Headless Services

    文章目录 一 服务发现机制 1 1 环境变量注入 1 2 DNS解析 二 Ingress 4 1 部署Ingress controller 4 2 暴露Ingress Controller 4 2 1 SVC NodePort方式 4 2
  • ElementUI 之el-form表单重置功能按钮

    业务场景 使用el form时 点击重置按钮或者取消按钮时会实现表单重置效果 重置功能按钮功能实现详细步骤 第一 首先给el form添加ref属性
  • 手把手教你做出数据可视化项目(四)动态模拟航班飞行路线

    数据可视化前言 https blog csdn net diviner s article details 115933789 项目最终效果图 此篇博客为自己学习pink老师的课后完成的项目的总结与记录 仅供交流参考 版权所有 转载请标注原
  • 服务器2016系统怎么添加用户名,windows-server-2016 – 如何在Nano Server中添加SMTP服务器角色?...

    使用 this TechNet page上的信息 我已经成功建立了一个远程PowerShell会话 其中包含在Hyper V VM中运行的2016 Preview 2 Nano Server 我现在想要添加SMTP服务器角色 我期待这是一个
  • android 史上最简单的下拉选择菜单DropDownMenu 几行代码轻松搞定!

    这是我在CSDN上第一篇原创文章 趁着从上家公司离职去考驾照的这段日子 想通过写技术博客的方式 锻炼一下自己的语言表达能力 以及对之前工作的总结 废话不多说了 直接进入正题 先给客官来张效果图 一 思路 下拉菜单首先让我想到了PopupWi
  • Java代码审计详解

    一 Fortify代码审计工具 1 Fortify简介 Fortify是Micro Focus旗下AST 应用程序安全测试 产品 其产品组合包括 Fortify Static Code Analyzer提供静态代码分析器 SAST Fort
  • 每日一道Leetcode——按奇偶排序数组II

    题目 我的解法一 双端队列 思路 用两个双端队列分别存储奇数和偶数 然后依次取一个 class Solution public int sortArrayByParityII int A Deque
  • 拓世AI

    2023年的小红书 发展趋势依旧昂扬向上 最新数据显示 小红书拥有逾3亿的月活用户 且超过80 的用户集中在20 30岁年龄段 这代表什么 广大的年轻用户基数和消费能力 正处于购买力上升期的年轻人 是品牌最想抓住的目标用户 巨大的红利吸引了
  • Visual C# 2010 实现菜单项和状态栏

    演练 向窗体提供标准菜单项 Visual Studio 2010 其他版本 此主题尚未评级 评价此主题 可以通过 MenuStrip 控件为窗体提供标准菜单 此演练演示如何使用 MenuStrip 控件创建标准菜单 窗体还将在用户选择菜单项
  • karatsuba大数乘法问题及其高效算法

    转载自 iTimeTraveler博客 题目 编写两个任意位数的大数相乘的程序 给出计算结果 比如 题目描述 输出两个不超过100位的大整数的乘积 输入 输入两个大整数 如1234567 和 123 输出 输出乘积 如 151851741