[英]Algorithms: Interesting diffing algorithm
這出現在現實世界的情況下,我想我會分享它,因為它可能會導致一些有趣的解決方案。 本質上,算法需要區分兩個列表,但是讓我給出一個更嚴格的問題定義。
假設您有兩個列表, L
和R
每個列表包含來自某些底層字母S
元素。 此外,這些列表具有它們按順序出現的公共元素的屬性:也就是說,如果L[i] = R[i*]
且L[j] = R[j*]
,並且i
< j
那么i
* < j
*。 列表根本不需要任何共同元素,並且一個或兩個可以是空的。 [ 澄清:你可以假設沒有重復的元素。 ]
問題是產生一種列表的“差異”,可以將其視為有序對(x,y)
新列表,其中x
來自L
, y
來自R
,具有以下屬性:
x
出現在兩個列表中,則結果中會出現(x,x)
。 x
出現在L
,而不出現在R
,則結果中會出現(x,NULL)
。 y
出現在R
,但不出現在L
,則結果中會出現(NULL,y)
。 最后
L = (d)
R = (a,b,c)
Result = ((NULL,d), (a,NULL), (b,NULL), (c,NULL))
L = (a,b,c,d,e)
R = (b,q,c,d,g,e)
Result = ((a,NULL), (b,b), (NULL,q), (c,c), (d,d), (NULL,g), (e,e))
有沒有人有任何好的算法來解決這個問題? 復雜性是什么?
如果您願意在不同的數據結構中復制其中一個列表,則可以在O(n)中執行此操作。 這是一個經典的時間/空間權衡。
創建列表R的哈希映射,其中鍵是元素,值是數組的原始索引; 在C ++中,您可以使用tr1中的unordered_map或boost。
保留列表R的未處理部分的索引,初始化為第一個元素。
對於列表L中的每個元素,檢查列表R中匹配的哈希映射。如果找不到,則輸出(L值,NULL)。 如果匹配,則從哈希映射中獲取相應的索引。 對於列表R中的每個未處理元素,直到匹配索引,輸出(NULL,R值)。 對於匹配,輸出(值,值)。
當您到達列表L的末尾時,請瀏覽列表R的剩余元素並輸出(NULL,R值)。
編輯:這是Python的解決方案。 對於那些說這個解決方案取決於是否存在良好的散列函數的人 - 當然它確實如此。 如果這是一個問題,原始海報可能會對問題增加額外的限制,但在此之前我會采取樂觀的態度。
def FindMatches(listL, listR):
result=[]
lookupR={}
for i in range(0, len(listR)):
lookupR[listR[i]] = i
unprocessedR = 0
for left in listL:
if left in lookupR:
for right in listR[unprocessedR:lookupR[left]]:
result.append((None,right))
result.append((left,left))
unprocessedR = lookupR[left] + 1
else:
result.append((left,None))
for right in listR[unprocessedR:]:
result.append((None,right))
return result
>>> FindMatches(('d'),('a','b','c'))
[('d', None), (None, 'a'), (None, 'b'), (None, 'c')]
>>> FindMatches(('a','b','c','d','e'),('b','q','c','d','g','e'))
[('a', None), ('b', 'b'), (None, 'q'), ('c', 'c'), ('d', 'd'), (None, 'g'), ('e','e')]
最壞的情況,如定義和僅使用相等,必須是O(n * m)。 考慮以下兩個列表:
A [] = {a,b,c,d,e,f,g}
B [] = {h,i,j,k,l,m,n}
假設這兩個“有序”列表之間只存在一個匹配。 它將進行O(n * m)比較,因為不存在比較,這消除了以后進行其他比較的需要。
所以,你提出的任何算法都將是O(n * m),或更糟。
通過遍歷列表和匹配,可以在線性時間內完成差異排序列表。 我將嘗試在更新中發布一些偽造的Java代碼。
由於我們不知道排序算法並且無法確定基於小於或大於運算符的任何排序,因此我們必須考慮無序列表。 另外,考慮到如何格式化結果,您將面臨掃描兩個列表(至少在您找到匹配項之前,然后您可以添加書簽並再次從那里開始)。 它仍然是O(n ^ 2)性能,或更具體地是O(nm)。
這與序列比對完全一樣,您可以使用Needleman-Wunsch算法來解決它。 該鏈接包含Python中的代碼。 只需確保設置得分,使得不匹配為負且匹配為正,並且最大化時與空白的對齊為0。 該算法在O(n * m)時間和空間中運行,但是可以改善其空間復雜度。
評分功能
int score(char x, char y){
if ((x == ' ') || (y == ' ')){
return 0;
}
else if (x != y){
return -1;
}
else if (x == y){
return 1;
}
else{
puts("Error!");
exit(2);
}
}
碼
#include <stdio.h>
#include <stdbool.h>
int max(int a, int b, int c){
bool ab, ac, bc;
ab = (a > b);
ac = (a > c);
bc = (b > c);
if (ab && ac){
return a;
}
if (!ab && bc){
return b;
}
if (!ac && !bc){
return c;
}
}
int score(char x, char y){
if ((x == ' ') || (y == ' ')){
return 0;
}
else if (x != y){
return -1;
}
else if (x == y){
return 1;
}
else{
puts("Error!");
exit(2);
}
}
void print_table(int **table, char str1[], char str2[]){
unsigned int i, j, len1, len2;
len1 = strlen(str1) + 1;
len2 = strlen(str2) + 1;
for (j = 0; j < len2; j++){
if (j != 0){
printf("%3c", str2[j - 1]);
}
else{
printf("%3c%3c", ' ', ' ');
}
}
putchar('\n');
for (i = 0; i < len1; i++){
if (i != 0){
printf("%3c", str1[i - 1]);
}
else{
printf("%3c", ' ');
}
for (j = 0; j < len2; j++){
printf("%3d", table[i][j]);
}
putchar('\n');
}
}
int **optimal_global_alignment_table(char str1[], char str2[]){
unsigned int len1, len2, i, j;
int **table;
len1 = strlen(str1) + 1;
len2 = strlen(str2) + 1;
table = malloc(sizeof(int*) * len1);
for (i = 0; i < len1; i++){
table[i] = calloc(len2, sizeof(int));
}
for (i = 0; i < len1; i++){
table[i][0] += i * score(str1[i], ' ');
}
for (j = 0; j < len1; j++){
table[0][j] += j * score(str1[j], ' ');
}
for (i = 1; i < len1; i++){
for (j = 1; j < len2; j++){
table[i][j] = max(
table[i - 1][j - 1] + score(str1[i - 1], str2[j - 1]),
table[i - 1][j] + score(str1[i - 1], ' '),
table[i][j - 1] + score(' ', str2[j - 1])
);
}
}
return table;
}
void prefix_char(char ch, char str[]){
int i;
for (i = strlen(str); i >= 0; i--){
str[i+1] = str[i];
}
str[0] = ch;
}
void optimal_global_alignment(int **table, char str1[], char str2[]){
unsigned int i, j;
char *align1, *align2;
i = strlen(str1);
j = strlen(str2);
align1 = malloc(sizeof(char) * (i * j));
align2 = malloc(sizeof(char) * (i * j));
align1[0] = align2[0] = '\0';
while((i > 0) && (j > 0)){
if (table[i][j] == (table[i - 1][j - 1] + score(str1[i - 1], str2[j - 1]))){
prefix_char(str1[i - 1], align1);
prefix_char(str2[j - 1], align2);
i--;
j--;
}
else if (table[i][j] == (table[i - 1][j] + score(str1[i-1], ' '))){
prefix_char(str1[i - 1], align1);
prefix_char('_', align2);
i--;
}
else if (table[i][j] == (table[i][j - 1] + score(' ', str2[j - 1]))){
prefix_char('_', align1);
prefix_char(str2[j - 1], align2);
j--;
}
}
while (i > 0){
prefix_char(str1[i - 1], align1);
prefix_char('_', align2);
i--;
}
while(j > 0){
prefix_char('_', align1);
prefix_char(str2[j - 1], align2);
j--;
}
puts(align1);
puts(align2);
}
int main(int argc, char * argv[]){
int **table;
if (argc == 3){
table = optimal_global_alignment_table(argv[1], argv[2]);
print_table(table, argv[1], argv[2]);
optimal_global_alignment(table, argv[1], argv[2]);
}
else{
puts("Reqires to string arguments!");
}
return 0;
}
樣本IO
$ cc dynamic_programming.c && ./a.out aab bba
__aab
bb_a_
$ cc dynamic_programming.c && ./a.out d abc
___d
abc_
$ cc dynamic_programming.c && ./a.out abcde bqcdge
ab_cd_e
_bqcdge
這是一個非常簡單的問題,因為您已經有了一個有序列表。
//this is very rough pseudocode
stack aList;
stack bList;
List resultList;
char aVal;
char bVal;
while(aList.Count > 0 || bList.Count > 0)
{
aVal = aList.Peek; //grab the top item in A
bVal = bList.Peek; //grab the top item in B
if(aVal < bVal || bVal == null)
{
resultList.Add(new Tuple(aList.Pop(), null)));
}
if(bVal < aVal || aVal == null)
{
resultList.Add(new Tuple(null, bList.Pop()));
}
else //equal
{
resultList.Add(new Tuple(aList.Pop(), bList.Pop()));
}
}
注意......此代碼不會編譯。 它只是作為指導。
編輯基於OP評論
如果未公開排序算法,則必須將列表視為無序。 如果列表是無序的,則算法具有O(n ^ 2)的時間復雜度,特別是O(nm),其中n和m是每個列表中的項目數。
EDIT算法來解決這個問題
L(a,b,c,d,e)R(b,q,c,d,g,e)
//pseudo code... will not compile
//Note, this modifies aList and bList, so make copies.
List aList;
List bList;
List resultList;
var aVal;
var bVal;
while(aList.Count > 0)
{
aVal = aList.Pop();
for(int bIndex = 0; bIndex < bList.Count; bIndex++)
{
bVal = bList.Peek();
if(aVal.RelevantlyEquivalentTo(bVal)
{
//The bList items that come BEFORE the match, are definetly not in aList
for(int tempIndex = 0; tempIndex < bIndex; tempIndex++)
{
resultList.Add(new Tuple(null, bList.Pop()));
}
//This 'popped' item is the same as bVal right now
resultList.Add(new Tuple(aVal, bList.Pop()));
//Set aVal to null so it doesn't get added to resultList again
aVal = null;
//Break because it's guaranteed not to be in the rest of the list
break;
}
}
//No Matches
if(aVal != null)
{
resultList.Add(new Tuple(aVal, null));
}
}
//aList is now empty, and all the items left in bList need to be added to result set
while(bList.Count > 0)
{
resultList.Add(new Tuple(null, bList.Pop()));
}
結果集將是
L(a,b,c,d,e)R(b,q,c,d,g,e)
結果((a,null),(b,b),(null,q),(c,c),(d,d),(null,g),(e,e))
沒有真正有形的答案,只有模糊的直覺。 因為您不知道排序算法,只知道數據在每個列表中排序,它聽起來有點像用於“差異”文件的算法(例如在Beyond Compare中)並將線序列匹配在一起。 或者也與regexp算法模糊相似。
也可以有多種解決方案。
(沒關系,如果沒有嚴格排序的重復元素,那就不行了。我在文件比較方面的想法太多了)
我認為你沒有足夠的信息。 所有你斷言的是匹配的元素以相同的順序匹配,但找到第一個匹配對是O(nm)操作,除非你有其他一些你可以確定的順序。
SELECT distinct l.element,r.element
來自LeftList l
OUTER JOIN RightList r
ON l.element = r.element
ORDER BY l.id,r.id
假設每個元素的ID是它的排序。 當然,您的列表包含在關系數據庫中:)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.