分类 杂谈 下的文章

一些容易混淆的时间复杂度分析


前言

时间复杂度分析简单来说大概分为这么几种情况:

  • 一层循环就是一层O(N), 循环套循环需要相乘
  • 如果是树状结构, 比如说有分治(二叉搜索, 快排)的时候, 那么就是O(logN)
  • 如果有递归:

    • 如果是尾递归, 那么其实是和循环等价的
    • 如果是树状递归, 比如说斐波拉契数列, 那么就是O(2^N), 确切的说是O(branches^depth)

Example1

Suppose we had an algorithm that took in an array of strings, sorted each string, and then sorted the full array. What would the runtime be?

Let's define new terms-and use names that are logical.

  • Let s be the length of the longest string.
  • Let a be the length of the array.

Now we can work through this in parts:

  • Sorting each string is O(s log s).
  • We have to do this for every string (and there are a strings), so that's O(a*s log s).
  • Now we have to sort all the strings. There are a strings, so you'll may be inclined to say that this takes O(a log a) time. This is what most candidates would say. You should also take into account that you need to compare the strings. Each string comparison takes O(s) time. There are O(a log a) comparisons, therefore this will take O(a*s log a) time.

If you add up these two parts, you get 0(a*s (log a + log s)).

这个很容易误认为是O(N^2 logN), 一方面是string的长度和array的长度并不一样, 另外一方面是在array排序的时候很容易忽略字符串比较还需要花费时间.

Example2

The following code prints all Fibonacci numbers from 0 to n. What is its time complexity?
void allFib(int n) {
    for (int i= 0; i < n; i++) {
        System.out.println(i + ": "+ fib(i));
    }
}

int fib(int n) {
    if (n <= 0) return 0;
    else if (n == 1) return 1;
    return fib(n - 1) + fib(n - 2);
} 

Instead, let's walk through each call.

  • fib(!) -> 2^1 steps
  • fib(2) -> 2^2 steps
  • fib(3) -> 2^3 steps
  • fib(4) -> 2^4 steps
  • fib(n) -> 2^n steps

Therefore, the total amount of work is: 2^1 + 2^2 + 2^3 + 2^4 + ... + 2^N = 2^(N+1)

这个很容易误认为是O(N*2^N), 主要是fib(n)这里的n每次循环都是变化的...很容易被误导

总结

时间复杂度的求法并不是很难, 基本就那么几种情况, 需要注意的是处理字符串时不能忽略掉字符串的长度.

Reference

Cracking the Coding Interview 6th Edition


MTJ-N + Intel MKL的配置和使用


前情提要

众所周知, 在python的世界, 矩阵库最好用的莫过于numpy, 类似于Matlab的语法, 让很多人从Matlab迁移过来毫无压力.

然而, 在Java的世界, 虽然以Spring为主的Web框架非常强大, 但是科学计算库的支持相比之下就相当感人. 倒并不是缺乏支持, 而是你很难找到一个足够好用效率又高支持又全的矩阵库.

当然实际上, 对于矩阵库的使用还是以效率为主, 所以今天要介绍的是Java世界中效率最高的矩阵库之一matrix-toolkits-java, 虽然作者已经不再维护, 但是性能经过实测依然吊打其他所有.

这个库性能高的主要原因在于它使用了Native库, 相比同样使用Native库的JBLAS, 这个库因为能够支持Intel MKL和Nvidia CUDA, 使其性能更高, 相关的Java矩阵库比较可以在Java Matrix Benchmark这里看到详细的比较.

正片开始

MTJ配置使用

MTJ在Maven中心仓库里, 所以如果使用Maven来管理依赖的话, 那么只要简单的添加即可:

<dependency>
    <groupId>io.github.andreas-solti.matrix-toolkits-java</groupId>
    <artifactId>mtj</artifactId>
    <version>1.0.7</version>
</dependency>

这里用的是另外一个Fork, 似乎是修正了原作者版本的一些问题, 不过没仔细看. 关于MTJ的文档可以参考这个: http://www.javadoc.io/doc/io.github.andreas-solti.matrix-toolkits-java/mtj/1.0.7.

使用这个库务必需要注意的是很多操作都是不复制而是在原矩阵上修改的, 例如:

public Matrix add(double alpha, Matrix B) {
    checkSize(B);

    if (alpha != 0)
        for (MatrixEntry e : B)
            add(e.row(), e.column(), alpha * e.get());

    return this;
}

public void add(int row, int column, double value) {
    set(row, column, value + get(row, column));
}

所以使用时如果要符合一般的使用习惯请务必注意:

/**
 * Simplify Some DenseMatrix Operation
 */
private static class DenseMatrixUtil{
    // return C=A+B
    public static DenseMatrix add(DenseMatrix A, DenseMatrix B){
        return (DenseMatrix)A.copy().add(B);
    }

    // return A=A+B
    public static DenseMatrix addi(DenseMatrix A, DenseMatrix B){
        return (DenseMatrix)A.add(B);
    }

    // return C=A-B
    public static DenseMatrix sub(DenseMatrix A, DenseMatrix B){
        return (DenseMatrix)A.copy().add(-1, B);
    }

    // return A=A-B
    public static DenseMatrix subi(DenseMatrix A, DenseMatrix B){
        return (DenseMatrix)A.add(-1, B);
    }

    // return C=s*A
    public static DenseMatrix mul(DenseMatrix A, double s){
        return (DenseMatrix)A.copy().scale(s);
    }

    // return A=s*A
    public static DenseMatrix muli(DenseMatrix A, double s){
        return (DenseMatrix)A.scale(s);
    }

    // return C=A*B
    private static DenseMatrix mmul(DenseMatrix A, DenseMatrix B){
        DenseMatrix C = new DenseMatrix(A.numRows(), B.numColumns()); // C.rows = A.rows, C.cols = B.cols
        return (DenseMatrix)A.mult(B, C);
    }

    // Compute \sum{alpha[i] * matrix[i]}
    private static DenseMatrix linear(double[] alphas, DenseMatrix[] matrixs){
        if(alphas.length != matrixs.length){
            throw new IllegalArgumentException("alpha's length should equal matrix's length");
        }

        DenseMatrix result = (DenseMatrix)new DenseMatrix(matrixs[0].numRows(), matrixs[0].numColumns()).zero();
        for(int i = 0; i < alphas.length; i++){
            result.add(alphas[i], matrixs[i]); // result = alpha * matrix + result
        }
        return result;
    }
}

与Intel MKL整合

Intel MKL全称是Intel® Math Kernel Library, 是Intel为其处理器设计的一套高度优化的Native矩阵与线性运算函数库, 相比OpenBLAS有着高得多的性能. 至于AMD处理器能不能使用, 这个我就不知道了, 但是我还是要喊出 AMD YES! (逃

Intel MKL安装

在Linux下, 只要直接从源里直接安装即可, 不同发行版不一样搜索一下即可, 以ArchLinux为例:

pacman -S intel-mkl

Arch的官方源里是没有MKL的, 如果需要的话, 请使用ArchlinuxCN或者Arch4Edu源.

在Windows下, 只能从官网注册开发者然后申请, 几天之后会给你发邮件然后提供下载连接下载Windows版的安装包.
这个安装包是包含devel部分的开发包, 事实上我们只需要redist部分即可, 为了方便使用我打包了2018.3.210的redist包(x86/x64). 事实上, Anaconda里面也是带Intel MKL Redist的, 完全可以从里面扒出来...

Intel MKL配置

在安装好Intel MKL以后还需要做两件事情:

  1. mkl\mkl_rt.dll软链接或者复制为libblas3.dllliblapack3.dll
  2. mklcompiler两个文件夹添加到系统的PATH环境变量中

这样, 才能够被MTJ-N所识别并使用.

如果不使用Intel MKL, MTJ似乎会自动使用OpenBLAS的实现, 其输出是下面这样的:

一月 14, 2019 11:31:15 上午 com.github.fommil.netlib.BLAS <clinit>
警告: Failed to load implementation from: com.github.fommil.netlib.NativeSystemBLAS
一月 14, 2019 11:31:15 上午 com.github.fommil.jni.JniLoader liberalLoad
信息: successfully loaded C:\Users\$USER\AppData\Local\Temp\jniloader7616017108689444760netlib-native_ref-win-x86_64.dll
一月 14, 2019 11:31:33 上午 com.github.fommil.netlib.LAPACK <clinit>
警告: Failed to load implementation from: com.github.fommil.netlib.NativeSystemLAPACK
一月 14, 2019 11:31:33 上午 com.github.fommil.jni.JniLoader load
信息: already loaded netlib-native_ref-win-x86_64.dll

性能虽然会比纯Java实现要好, 但是与MKL相比之下就要逊色很多了.

如果正确识别到MKL的话, 应该会类似的输出:

一月 14, 2019 11:38:25 上午 com.github.fommil.jni.JniLoader liberalLoad
信息: successfully loaded C:\Users\$USER\AppData\Local\Temp\jniloader3275505562389233726netlib-native_system-win-x86_64.dll
一月 14, 2019 11:38:26 上午 com.github.fommil.jni.JniLoader load
信息: already loaded netlib-native_system-win-x86_64.dll

微不足道的总结

如果要做科学计算方面的工作的话, 还是:

人生苦短, 快用Python (并不

给博客增加MathJax支持


MathJax是一个非常著名的用于在网页中显示公式的JS库, 主要是你用类似于>$\LaTeX$<的语法写下公式他就会自动帮你渲染非常方便. 比如:

$$ E=mc^2 $$

或者更加复杂一点:

$$ \normalsize \varepsilon=\sum_{i=1}^{n-1} \frac1{\Delta x}\int\limits_{x_i}^{x_{i+1}}\left\{\frac1{\Delta x}\big[ (x_{i+1}-x)y_i^\ast+(x-x_i)y_{i+1}^\ast\big]-f(x)\right\}^2dx $$

以前我也弄过这个, 不过用的是第三方的插件, 然后和Markdown混合渲染问题也很多, 最近发现直接在footer.php里添加脚本来加载就好了. 地址用的是一个国内的CDN, 有空再把地址配置到服务器上去把Orz

然后我把行内触发的标签从$$改成了>$$<, 因为两个美元符号还是蛮容易误触发的...

<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
        extensions: ["tex2jax.js"],
        jax: ["input/TeX", "output/HTML-CSS"],
        tex2jax: {
            inlineMath: [ ['>$','$<']],
            displayMath: [ ['$$','$$']],
            processEscapes: true
        },
        "HTML-CSS": { fonts: ["TeX"] }
    });
</script>
<script src='https://cdn.bootcss.com/mathjax/2.7.5/latest.js?config=TeX-MML-AM_CHTML' async></script>

顺便推荐一个MathTeX的参考网站参考书籍, 非常好使


如何将Markdown转换为MediaWiki


起因

虽然Markdown不是完美的标记语言, 或多或少有一些不足, 但是MediaWiki是真的难用! 所以如何将Markdown写好的文档转换为MediaWiki就变成了一个问题.

根据调研发现了Pandoc这样一个工具可以在任意两种文档之间进行转换, 具体支持哪些可以去官方的介绍界面去看, 当然我们只要知道它可以做到Markdown2MediaWiki的转换就好了.

这种转换经过测试, 基本上还可以, 就是代码块的转换上会有一点小问题, 不过这个会在后文给出正则表达式进行替换一下就好了.

使用方法

  1. 安装pandoc: https://pandoc.org/installing.html

    • WSL用户可以直接在WSL里使用包管理器安装源里的版本, 简单省事
  2. 打开终端(CMD)切换目录到源文件目录, 然后运行pandoc markdown.md -f markdown -t mediawiki -o mediawiki.wiki就可以完成文档的转换

    • Markdown的源文件是markdown.md, 输出文件是mediawiki.wiki, 当然可以自定义

一些问题

对代码块的处理有问题

pandonc默认是将```的代码块标记转换为HTML的<code>标签, 但是MediaWiki是不支持这个标签的, 因此需要将其转换为<pre>标签.

示例正则表达式如下:

Find: (</*)source([^>]*>)
Replace: \1pre\2

在Sublime下测试过没问题, 如果使用其正则表达式引擎特别是sed请自行测试修改.

总结

MediaWiki真的难用啊啊啊啊锕啊啊


利用VSCode构建简单的>$\LaTeX$<编辑器


起因

起因是因为要开始论文初稿的撰写, 然后需要使用>$\LaTeX$<. 虽然老板想让我在OverLeaf上写, 但是作为一个Vim党, 可能习惯了Vim的操作习惯以后就再也回不到纯文本的编辑上去了吧[笑]. 抛弃掉TexLive自带的TexWorks也是同理, VSCode的Vim插件总的来说还是挺好用的(虽然由于VSCode本身机制的问题会和一些插件不兼容, 比如acejump.

经过

现在是8102年了, 所以你需要的安装的是TexLive而不是CTex. 事实上, 对于中文现在TexLive也可以正常处理, 只要配置好字体就可以了1, 而且CTex也已经很久都没有更新过. 至于其他的发行版, 我只要求能用而且足够好用就可以了所以懒得圣战.

所以你需要的工具有:

安装方法非常简单:

  1. 安装VScode
  2. 安装TexLive2
  3. 安装Perl
  4. 打开终端运行tex看是否找到命令
  5. 安装VSCode的LaTeX Workshop插件
  6. 输入下列测试代码1保存:

    \documentclass[UTF8]{ctexart}
    \begin{document}
    你好,world!
    \end{document}
  7. 尝试运行左侧边栏的Build LaTeX Project - Recipe: latexmk/Recipe: pdflatex -> bibtex -> pdflatex*2应该都能够输出pdf
  8. 尝试运行左侧边栏的View LaTeX PDF - View in VSCode Tab/View in web browser就可以分别在VSCode的新标签页和浏览器打开了, 而且是会根据你编辑的内容实时刷新哟

其他的一些可能设置:

  • 输出到指定目录(比如./output)而不是根目录

    • 修改latex-workshop.latex.tools

          {
              "name": "latexmk",
              "command": "latexmk",
              "args": [
                  "-synctex=1",
                  "-interaction=nonstopmode",
                  "-file-line-error",
                  "-pdf",
                  "--outdir=output",
                  "%DOC%"
              ]
          },
    • 修改latex-workshop.latex.outputDir./output即可
  • 编译后清理掉中间文件

    • 修改latex-workshop.latex.tools

          {
              "name": "latexmk_build",
              "command": "latexmk",
              "args": [
                  "-synctex=1",
                  "-interaction=nonstopmode",
                  "-file-line-error",
                  "-pdf",
                  "--outdir=output",
                  "%DOC%"
              ]
          },
          {
              "name": "latexmk_clean",
              "command": "latexmk",
              "args": [
                  "-c",
                  "--outdir=output",
                  "%DOC%"
              ]
          },
    • 修改latex-workshop.latex.recipes

          {
              "name": "latexmk",
              "tools": [
                  "latexmk_build",
                  "latexmk_clean"
              ]
          },
  • 比如想要使用xelatex来排版

    • 打开VSCode的配置文件
    • latex-workshop.latex.recipes中加入如下内容:

      {
          "name": "xelatex",
          "tools": [
              "xelatex"
          ]
      },
    • latex-workshop.latex.tools中加入如下内容:

      {
          "name": "xelatex -> bibtex -> xelatex*2",
          "tools": [
              "xelatex",
              "bibtex",
              "xelatex",
              "xelatex"
          ]
      },
      {
          "name": "xelatex",
          "tools": [
              "xelatex",
          ]
      },
    • 重启VScode, 你就可以在Build LaTeX Project下看到新添加的Recipe了~

      xelatex -> bibtex -> xelatex*2用于处理需要bbl的情况, xelatex则用于纯>$\LaTeX$<环境下使用.
  • 在工作区排除掉LaTeX编译中间文件的显示

    • 加入如下配置到配置文件即可:

      "files.exclude": {
          "**/*.aux": true,
          "**/*.fdb_latexmk": true,
          "**/*.fls": true,
          "**/*.log": true,
          "**/*.synctex.gz": true,
          "**/*.bbl": true,
          "**/*.blg": true
      },
  • 补充一下自动clean的文件

    • latex-workshop.latex.clean.fileTypes中加入如下内容:

      "latex-workshop.latex.clean.fileTypes": [
          "*.aux",
          "*.bbl",
          "*.blg",
          "*.idx",
          "*.ind",
          "*.lof",
          "*.lot",
          "*.out",
          "*.toc",
          "*.acn",
          "*.acr",
          "*.alg",
          "*.glg",
          "*.glo",
          "*.gls",
          "*.ist",
          "*.fls",
          "*.log",
          "*.fdb_latexmk",
          "*.synctex.gz"
      ],
  • 其他的一些额外的有用的提示

    • 在VSCode的输出面板有LaTex WorkshopLaTeX Compiler两个输出选项, LaTex Workshop是LaTeX输出插件的日志, LaTeX Compiler是LaTeX编译器的输出日志
    • 如果没有pdf文件输出请检查编译器的日志, 查看VSCode是否找到了编译器是否正确编译
    • 如果有pdf文件, 但是pdf文件没有被打开请检查插件日志, 默认是打开主tex文件下同名pdf文件, 如果配置了输出目录需要修改配置latex-workshop.latex.outputDir

总结

VSCode不愧是一个全能型轻量级IDE, 通过各种插件的组合能够实现IDE的基本功能, 当然缺点主要是打开反应慢(和Sublime相比), 而且有些操作逻辑很迷(比如拖出标签页是保存文件???).

不过通过这一系列配置之后基本能实现实时预览和编写, 而且支持VScode完善的现代编辑器操作, 还是非常好评的w