diff命令实现

diff命令实现

diff是类UNIX系统下的一个重要的系统工具,用于比较两个文本文件的差异。

它有三种输出格式

先给大家看看两个用于比对的文件原文

file1:
a
e
b
a
g
h
b
g
g

file2:
b
c
d
g
e
g
j
h

格式一,普通格式:

$ diff file1 file2
1,6d0
< a
< e
< b
< a
< g
< h
7a2,3
> c
> d
8a5
> e
9a7,8
> j
> h

格式二,上下文格式:

*** file1	2021-09-20 11:58:40.660690542 +0800
--- file2	2021-09-20 11:58:50.369119254 +0800
***************
*** 1,9 ****
- a
- e
- b
- a
- g
- h
  b
  g
  g
--- 1,8 ----
  b
+ c
+ d
  g
+ e
  g
+ j
+ h

格式三,合并格式:

--- file1	2021-09-20 11:58:40.660690542 +0800
+++ file2	2021-09-20 11:58:50.369119254 +0800
@@ -1,9 +1,8 @@
-a
-e
-b
-a
-g
-h
 b
+c
+d
 g
+e
 g
+j
+h

实现原理LCS

不管是哪一种输出,它们的核心原理是一致的

diff的实现原理是最长公共子序列算法(LCS)

接下来,以上面的字符串为例,对该算法进行解析

aebaghbgg
bcdgegjh

首先,最长公共子序列算法和最长子串算法是不一样的两个算法。子串要求不仅先后顺序一致,还要前驱后继也一致,而最长公共子序列算法只要求先后顺序一致。

例如,aebaaebaghbgg的一个子串,也是一个子序列,而abgg是其的一个子序列,而非子串

公共子序列,就是同时是两个字符串的子序列的一段序列

因此,最长公共子序列,就是两个字符串之间先后顺序一致的最长的一段序列

例如,上面的两个字符串的一个最长公共子序列是egh

a    e ba g   h bgg
bcdg e    g j h

知道了最长公共子序列后,我们就可以用最少的改动来使得两个文本一致

现在,我们对LCS算法进行一个理论的推导。

现在有两个字符串,一个字符串为p1p2p3p4...pm,另一个为q1q2q3q4...qn,设它们的一个最长子序列为r1r2r3r4...rk

可以显然得到以下几个性质:

  1. k的取值一定在0~ max(m, n)之间,若两个字符串完全没有相同的字符,则其取值为0,若顺序完全相同,则取值为字符串的长度
  2. pm等于qn,那么,rk也一定等于pmqn。因为最长公共子序列是最长的,而r已经是一个最长公共子序列了
  3. pm不等于qn,那么有这几种情况:

rk不等于pm,则r1r2r3...rkp1p2p3...p(m-1)q1q2q3...qn之间的最长公共子序列。举例,acdeabcd,它们的最长公共子序列显然是acd,第一个字符串的d后面的e就已经和最长公共子序列没关系了
同理,rk不等于qn时,r1r2r3...rkp1p2p3...pmq1q2q3...q(n-1)之间的最长公共子序列

很显然,这个性质可以用于推导递归公式

我们设L(m, n)是字符串p1p2...pmq1q2...qn之间的最长子序列的长度
m为0或者n为0的时候,即两个字符串有一个为空串,则显然没有最长公共子序列,L(0, 0)就是0

m不等于0且n不等于0时,若pm等于pn则,L(m, n)=L(m-1, n-1)。继续拿上面的acdeabcd举例,现在它们的最长公共子序列是acd,当我们在后面再添加一个一样的字符时,例如n,则acdenabcdn的最长公共子序列就成了acdn,长度增长了1

pm不等于pn,要么L(m-1, n)是最长的,要么L(m, n-1)是最长的

因此,递推公式可以写为:

L(m, n) = 
	0, m == 0 || j == 0;
	L(m - 1, m - 1) + 1, m > 0 && n > 0 && p[m] == q[n];
	max(L(m, n - 1), L(m - 1, n)), m > 0 && n > 0 && p[m] != q[n]

以最开始的

aebaghbgg
bcdgegjh

举例

m为0或n为0时,取值为0,有L(0...m, 0) = L(0, 0...n) = 0
mn均不为0时,以L(1...3, 0...n)举例

a != b, L(1, 1) = max(L(1, 0), L(0, 1)) = 0;
a != c, L(1, 2) = max(L(1, 1), L(0, 2)) = 0;

a != h, L(1, 8) = max(L(1, 7), L(0, 8)) = 0;

e != b, L(2, 1) = max(L(2, 0), L(1, 1)) = 0;

e == e, L(2, 5) = L(1, 4) + 1 = 1;
e != g, L(2, 6) = max(L(1, 6), L(2, 5)) = 1;
e != j, L(2, 7) = max(L(1, 7), L(2, 6)) = 1;

b == b, L(3, 1) = L(2, 0) + 1 = 1;
b != c, L(3, 2) = max(L(2, 2), L(3, 1)) = 1;

将全部结果写作表格形式:

 | |0 1 2 3 4 5 6 7 8
 | | |b|c|d|g|e|g|j|h
-|-------------------
0| |0 0 0 0 0 0 0 0 0
 |-
1|a|0 0 0 0 0 0 0 0 0
 |-
2|e|0 0 0 0 0 1 1 1 1
 |-
3|b|0 1 1 1 1 1 1 1 1
 |-
4|a|0 1 1 1 1 1 1 1 1
 |-
5|g|0 1 1 1 2 2 2 2 2
 |-
6|h|0 1 1 1 2 2 2 2 3
 |-
7|b|0 1 1 1 2 2 2 2 3
 |-
8|g|0 1 1 1 2 2 3 3 3
 |-
9|g|0 1 1 1 2 2 3 3 3

可得这两个字符串的最长公共子序列长度为3

但是,只知道长度是不够的,我们还需要获取对应的具体的子序列,才能知道哪些是需要更改的、哪些是维持原样就好的

我们可以借助上面的表格进行逆推

L(9, 8)=3,查看发现g != h,则它的值要么是从L(8, 8)继承来的,要么是从L(9, 7)
因为L(8, 8) == L(9, 7) == 3,因此,任意选一个方向进行回溯都是可以的
这里,我们统一选择向上的方向,即回溯至L(8, 8)
g != h,重复上一步,回溯至L(7, 8)
b != h,重复上一步,回溯至L(6, 8)
h == h,因此,回溯序列的最后一位为h
L(6, 8)的值来自于L(5, 7),因此,我们回溯至L(5, 7)
g != j,回溯至L(5, 6)
g == g,回溯至L(4, 5),回溯序列gh
a != e,回溯至L(3, 5)
b != e,回溯至L(2, 5)
e == e,回溯至L(1, 4),回溯序列egh
再回溯,就全是空串了
因此,这两个字符串的一个最长公共子序列为egh

这只是一个结果,如果我们选择另一个方向进行回溯,结果又会发生不同
例如,我们若全部往左回溯,结果将是bgg,正好就是diff

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值