diff算法学习-cnblog
diff算法学习
diff在linux、git中都可以经常被看到,用来对比文件差异,特别是用过git的同学,对此肯定深有体会,那他究竟是如何做到的呢?
这里有一张github上面的图:

红色部分是删除部分,绿色部分是增加部分,剩下没有标记的则是没有改变部分。
在设计这个diff算法时,我们尽量遵循下面约定(从上到下优先级递增):
- 每个操作部分总是尽可能成块;
- 删除操作在增加操作之前;
- 整个操作尽可能少;
下面我们主要学习Myers Diff算法。
这还和算法比赛中的最短编辑距离不同,本质都是使用动态规划,但是Myers Diff算法使用了一定的启发式操作,用来平衡检查差异程度和生成速度。
Git Diff 算法详解:Myers Diff Algorithm-腾讯云开发者社区-腾讯云
| 场景 | 推荐算法 | 原因 |
|---|---|---|
| Git diff、文件对比 | Myers | 文件通常差异小,速度快 |
| DNA 序列比对 | Myers 或 专业算法 | 序列长,差异可能大 |
| 拼写检查(短词) | 经典 DP | 实现简单,长度短 |
| 文本编辑器 diff | Myers | 交互式需要实时响应 |
| 计算最短距离值 | DP 或 Myers | 两者都能,看需求 |
所以对于长序列,我们还是不能使用传统的DP算法来写,但是对于长度较短和快速开发时,我们可以选择使用DP。
最经典的就是Leetcode上的编辑距离那道题,就是使用的动态规划。
| DP | Myers | |
|---|---|---|
| 最好情况 | O(N×M) | O(D²) ⚡ |
| 最坏情况 | O(N×M) | O(N×M) |
| 空间 | 通常 O(min(N,M)) | O(N+M) ⚡ |
| 输出 | 需要额外处理 | 天然输出 ⚡ |
| 适用 | 短序列、小规模 | 长序列、版本控制 ⭐ |
题目描述:
给定两个字符串\(a\)和\(b\),我们要用最少的字符操作次数,将字符串\(a\)转换为字符串\(b\)。这里所说的字符操作共有三种:
- 删除一个字符;
- 增加一个字符;
- 将一个字符替换成另一个字符;
其实默认还有一种操作就是保持不变。
因为字符串的长度都小于等于500,所以我们有很大的操作空间。
状态定义:\(dp[i][j]\)表示考虑到\(a\)的前\(i\)个字符,\(b\)的前\(j\)个字符时,通过三种操作使得他们相同的最小操作次数;
转移方程:我们先考虑当前\(dp[i][j]\),他可以由什么转移过来呢?如果是删除一个字符\(a\)的最后一个字符,那么就是\(dp[i][j]=dp[i-1][j]+1\),也就是\(a[1\dots i-1]=b[1\dots j]\),那么此时对于\(a[i]\)就是多余的,我们把它删了;如果是增加一个字符,那么就是\(dp[i][j]=dp[i][j-1]+1\),也就是\(a[1\dots i]=b[1\dots j-1]\),现在增加一个\(b[j]\)在末尾;如果是替换,那么简单,\(dp[i][j]=dp[i-1][j-1]+1\),也即将\(a[i]\)替换成\(b[j]\)即可,但是如果\(a[i]=b[j]\),那么这个替换操作就是不用的。
综上:\(dp[i][j]=\min{(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+[a[i]\ne b[j]])}\)。
初始化:\(dp[i][0]=i,dp[0][j]=j\)。
class Solution {
public:
int minDistance(string word1, string word2) {
auto& a=word1,&b=word2;
int n=a.size(),m=b.size();
a=" "+a;
b=" "+b;
vector<vector<int>> dp(n+1,vector<int>(m+1,0));
for(int i=1;i<=n;i++) dp[i][0]=i;
for(int j=1;j<=m;j++) dp[0][j]=j;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
dp[i][j]=min({dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+(a[i]!=b[j])});
}
}
return dp[n][m];
}
};
其实处理一般的最短距离,我们只需要按照这样去实现就好。剩下的Meyer Diff算法我只是看了一下,还没有具体去自己写代码,等后面补一下。
大致原理是这样的:
常见的diff算法都是按照行为最小单位去处理的,我们把每一行算一个哈希值,作为这一行的指纹,这样对于新旧文件,我们就得到了两个序列,之后便是使用算法来处理了,直接使用dp主要是复杂度比较高,是\(O(nm)\)的,相对来说不够优秀,后面的其他算法主要思想都差不多,主要是启发式的策略可能不同。
后面可以自己实现一下这个算法,看一下效果。

浙公网安备 33010602011771号