http://www.matrix67.com/blog/archives/333

http://www.cnblogs.com/tangcong/archive/2012/09/10/2679081.html

除了字符串匹配、查找回文串、查找重复子串等经典问题以外,日常生活中我们还会遇到其它一些怪异的字符串问题。比如,有时我们需要知道给定的两个字符串“有多像”,换句话说两个字符串的相似度是多少。1965年,俄国科学家Vladimir Levenshtein给字符串相似度做出了一个明确的定义叫做Levenshtein距离,我们通常叫它“编辑距离”。字符串A到B的编辑距离是指,只用插入、删除和替换三种操作,最少需要多少步可以把A变成B。例如,从FAME到GATE需要两步(两次替换),从GAME到ACM则需要三步(删除G和E再添加C)。Levenshtein给出了编辑距离的一般求法,就是大家都非常熟悉的经典动态规划问题。
    在自然语言处理中,这个概念非常重要,例如我们可以根据这个定义开发出一套半自动的校对系统:查找出一篇文章里所有不在字典里的单词,然后对于每个单词,列出字典里与它的Levenshtein距离小于某个数n的单词,让用户选择正确的那一个。n通常取到2或者3,或者更好地,取该单词长度的1/4等等。这个想法倒不错,但算法的效率成了新的难题:查字典好办,建一个Trie树即可;但怎样才能快速在字典里找出最相近的单词呢?这个问题难就难在,Levenshtein的定义可以是单词任意位置上的操作,似乎不遍历字典是不可能完成的。现在很多软件都有拼写检查的功能,提出更正建议的速度是很快的。它们到底是怎么做的呢?1973年,Burkhard和Keller提出的BK树有效地解决了这个问题。这个数据结构强就强在,它初步解决了一个看似不可能的问题,而其原理非常简单。

首先,我们观察Levenshtein距离的性质。令d(x,y)表示字符串x到y的Levenshtein距离,那么显然:

1. d(x,y) = 0 当且仅当 x=y  (Levenshtein距离为0 <==> 字符串相等)
2. d(x,y) = d(y,x)     (从x变到y的最少步数就是从y变到x的最少步数)
3. d(x,y) + d(y,z) >= d(x,z)  (从x变到z所需的步数不会超过x先变成y再变成z的步数)

最后这一个性质叫做三角形不等式。就好像一个三角形一样,两边之和必然大于第三边。给某个集合内的元素定义一个二元的“距离函数”,如果这个距离函数同时满足上面说的三个性质,我们就称它为“度量空间”。我们的三维空间就是一个典型的度量空间,它的距离函数就是点对的直线距离。度量空间还有很多,比如Manhattan距离,图论中的最短路,当然还有这里提到的Levenshtein距离。就好像并查集对所有等价关系都适用一样,BK树可以用于任何一个度量空间。

建树的过程有些类似于Trie。首先我们随便找一个单词作为根(比如GAME)。以后插入一个单词时首先计算单词与根的Levenshtein距离:如果这个距离值是该节点处头一次出现,建立一个新的儿子节点;否则沿着对应的边递归下去。例如,我们插入单词FAME,它与GAME的距离为1,于是新建一个儿子,连一条标号为1的边;下一次插入GAIN,算得它与GAME的距离为2,于是放在编号为2的边下。再下次我们插入GATE,它与GAME距离为1,于是沿着那条编号为1的边下去,递归地插入到FAME所在子树;GATE与FAME的距离为2,于是把GATE放在FAME节点下,边的编号为2。
      
    查询操作异常方便。如果我们需要返回与错误单词距离不超过n的单词,这个错误单词与树根所对应的单词距离为d,那么接下来我们只需要递归地考虑编号在d-n到d+n范围内的边所连接的子树。由于n通常很小,因此每次与某个节点进行比较时都可以排除很多子树。
    举个例子,假如我们输入一个GAIE,程序发现它不在字典中。现在,我们想返回字典中所有与GAIE距离为1的单词。我们首先将GAIE与树根进行比较,得到的距离d=1。由于Levenshtein距离满足三角形不等式,因此现在所有离GAME距离超过2的单词全部可以排除了。比如,以AIM为根的子树到GAME的距离都是3,而GAME和GAIE之间的距离是1,那么AIM及其子树到GAIE的距离至少都是2。于是,现在程序只需要沿着标号范围在1-1到1+1里的边继续走下去。我们继续计算GAIE和FAME的距离,发现它为2,于是继续沿标号在1和3之间的边前进。遍历结束后回到GAME的第二个节点,发现GAIE和GAIN距离为1,输出GAIN并继续沿编号为1或2的边递归下去(那条编号为4的边连接的子树又被排除掉了)……
    实践表明,一次查询所遍历的节点不会超过所有节点的5%到8%,两次查询则一般不会17-25%,效率远远超过暴力枚举。适当进行缓存,减小Levenshtein距离常数n可以使算法效率更高。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<vector>
#include<string>
#include<math.h>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<algorithm>
using namespace std;int dp[40][40];
char s1[100], s2[100], st[10010][30];
const int inf  = 0x7f7f7f7f;
//数据结构定义
struct node
{char word[30]; //当前结点值node *next[30];
}root;node p[100000];
int num, flag, vnum, fuck;
map<string,int>mp;int f[100000];void init( )
{for( int i = 0; i < 40; i++)for( int j = 0; j < 40; j++)dp[i][j] = inf;
}int diff( char *s1, char *s2)
{init();int x = strlen(s1+1);int y = strlen(s2+1);for( int i = 0; i <= x; i++)dp[i][0] = i;for( int j = 0; j <= y; j++)dp[0][j] = j;for( int i = 1; i <= x; i++){for( int j = 1; j <= y; j++){dp[i][j] = min(min(dp[i-1][j]+1, dp[i][j-1]+1), dp[i-1][j-1]+ !(s1[i]==s2[j]) );}  }return dp[x][y];
} //建树
void insert(node *q, char *str)
{node *l = q;while( l ){int dis = diff( l->word, str);if( ! l->next[dis] ){l->next[dis] = &p[num++];strcpy(l->next[dis]->word + 1, str + 1);break;}l = l->next[dis];               }
}//查找与单词相差不大于d的单词
void sfind(node *q, char *str, int d)
{if( flag ) return ;node *l = q;if( l == NULL )return;int dis = diff(str, l->word);if( dis <= d ){fuck++;}for( int x = dis-d; x <= dis+d; x++){  if( x >= 0 && x <= 20 && l->next[x] )sfind(l->next[x], str, d);     }}int main( )
{int N, M, d, cnt, T, abc = 1;char str[1000];scanf("%d",&T);while( T-- ){scanf("%d%d",&N,&M);memset(p,0,sizeof(p));for( int i = 0; i < 30; i++)root.next[i] = NULL;num = 0;int cnum = 1;strcpy(st[0] + 1, root.word+1);for( int i = 1; i <= N; i++){scanf("%s",st[i]+1);insert(&root, st[i]);}d = 1;printf("Case #%d:\n", abc++);for( int i = 1; i <= M; i++){vnum = 0;flag = 0;fuck = 0;scanf("%s%d",str+1, &d);sfind(&root, str, d); printf("%d\n", fuck);}}return 0;
}

自己写的版本,比较容易理解

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sstream>
#include <set>
#include <algorithm>using namespace std;
#define MAXEDIT 15
class node {
public:string word;node *next[MAXEDIT];node() {memset(next, 0, sizeof(next));}
};string split(const string& str) {size_t pos = str.find(" ||| ");return str.substr(0, pos);
}bool isalpha(const string& str) {for (int i = 0; i < str.size(); ++i) {if (!(str[i]>='a' && str[i] <='z' || str[i]>='A' && str[i] <='Z' )) return false;}return true;
}int minTri(int a, int b, int c) {int rst = a;if (rst > b) rst = b;if (rst > c) rst = c;return rst;
}int editDist(const string &str1, const string &str2) {vector<vector<int> > mat(str1.size() + 1, vector<int>(str2.size() +1, 0));for (int i = 1; i < str1.size(); ++i) mat[i][0] = i;for (int i = 1; i < str2.size(); ++i) mat[0][i] = i;for (int i = 1; i <= str1.size(); ++i) {for (int j = 1; j <= str2.size(); ++j) {int cost = 1;if (str1[i-1] == str2[j-1]) cost = 0;mat[i][j] = minTri(mat[i-1][j-1]+cost, mat[i-1][j] + 1, mat[i][j-1] + 1);}}return mat[str1.size()][str2.size()];
}void insert(node* head, const string& str) {node *tmp = head;while (tmp) {int dis = editDist(tmp->word, str);if (dis == 0 || dis >= MAXEDIT) return;if (tmp->next[dis]) tmp = tmp->next[dis];else {tmp->next[dis] = new node();tmp->next[dis]->word = str;break;}}}void buildKDTree(node *head, const vector<string>& ls) {for (int i = 0; i < ls.size(); ++i) {insert(head, ls[i]);}
}void freeKDTree(node* head) {for (int i = 0; i < MAXEDIT; ++i) {if (head->next[i]) {freeKDTree(head->next[i]);delete head->next[i];head->next[i] = NULL;}}
}void findN(node *head, const string & str,vector<pair<string,int> >& rst, int n) {int d = editDist(head->word, str);if (d <= n && d != 0) {rst.push_back(make_pair(head->word,d));}int minR = max(1, d - n);int maxR = min(MAXEDIT-1, d + n);for (int i = minR; i <= maxR; ++i) {if (head->next[i]) {findN(head->next[i], str, rst, n);}}
}bool Cmp(const pair<string, int>& p1, const pair<string, int> &p2) {return p1.second < p2.second;
}int main(int argc, char *argv[]) {if (argc != 3) {cout << "input output"<<endl;return -1;}ifstream fin(argv[1]);ofstream fo(argv[2]);string line;set<string> st;while(getline(fin, line)) {string word = split(line);if (isalpha(word) && word.size() > 1)st.insert(word);}vector<string> ls(st.size());set<string>::iterator it = st.begin();int i = 0;for(; it != st.end(); ++it)ls[i++] = *it;node head;head.word = ls[0];buildKDTree(&head, ls);for (i = 0; i < ls.size();++i) {if ((i+1)%5000 ==0) cout << i+1<<endl;vector<pair<string, int> > rst;int dist = min((int)ls[i].size()/2, 3);findN(&head, ls[i], rst, dist);ostringstream ostr;ostr<<ls[i]<<"\t";sort(rst.begin(), rst.end(), Cmp);for (int j = 0; j < rst.size(); ++j) {ostr<<rst[j].first<<" ";}fo<<ostr.str()<<endl;}freeKDTree(&head);fin.close();fo.close();system("pause");return 0;
}

实际效果比之前写的多线程暴力慢多了.......

HDU 4323 bk树 编辑距离相关推荐

  1. 从编辑距离、BK树到文本纠错

    搜索引擎里有一个很重要的话题,就是文本纠错,主要有两种做法,一是从词典纠错,一是分析用户搜索日志,今天我们探讨使用基于词典的方式纠错,核心思想就是基于编辑距离,使用BK树.下面我们来逐一探讨: 编辑距 ...

  2. HDU 4323 Magic Number(编辑距离DP)

    http://acm.hdu.edu.cn/showproblem.php?pid=4323 题意: 给出n个串和m次询问,每个询问给出一个串和改变次数上限,在不超过这个上限的情况下,n个串中有多少个 ...

  3. 基于 BK 树的中文拼写纠错候选召回

    最近在研究中文拼写纠错,在查阅资料的时候看到了这篇文章<从编辑距离.BK树到文本纠错 - JadePeng - 博客园>,觉得 BK 树挺有意思的,决定深入研究一下,并在其基础上重新整理一 ...

  4. POJ 2777 ZOJ 1610 HDU 1698 --线段树--区间更新

    直接将这3题 放一起了  今天在做线段树的东西 这3个都是区间更新的 查询方式互相不同 反正都可以放到一起吧 直接先上链接了 touch me touch me touch me 关于涉及到区间的修改 ...

  5. HDU 6681(树状数组统计平面内射线的交点个数)

    HDU 6681(树状数组,统计平面内射线的交点个数) 题目链接:传送门 题意:给出k条射线,求射线将n∗mn*mn∗m 的区域分成几个联通块.每两条射线的端点x坐标和y坐标都互不相同. 思路:根据 ...

  6. hdu 2795 段树--点更新

    http://acm.hdu.edu.cn/showproblem.php?pid=2795 在第一和第三多学校都出现线段树,我在比赛中并没有这样做.,热身下,然后31号之前把那两道多校的线段树都搞了 ...

  7. hdu 5497 Inversion(树状数组)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5497 解题思路: 用树状数组维护一段区间L,区间长度为m,依次枚举该区间的终点ai,即将该点加入到区间 ...

  8. hdu 5367(线段树+区间合并)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5367 官方题解: 对于求"高山脉"长度,可以利用线段树.树节点中保存左高度连续长度 ...

  9. hdu 5266(线段树+LCA)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5266 解题思路: 考虑dfs序,通过在简单的证明可知L~R的LCA为L ~R 中dfs序较小的那个位置 ...

最新文章

  1. LeetCode实战:二叉树的最大深度
  2. Plant Com:中科院遗传发育所白洋组开发定量检测宿主微生物组的HA-QAP技术(王二涛点评)...
  3. jasp报错_JSP报错!成功解决问题200+追加分数!
  4. 7-53 两个有序序列的中位数 (25 分)(思路加详解)用STL容器中的set容器的自动去重过不去
  5. Linux多命令协作:管道及重定向
  6. Maven构建生命周期和各种plugin插件
  7. 中国内镜超声针市场趋势报告、技术动态创新及市场预测
  8. Thymeleaf模板的使用
  9. 汇编:在BUFFER中定义了的十个带符号字,将其中的负数变成绝对值,并以十进制方式输出
  10. centos6.0的gnome桌面的一个大bug
  11. Understanding Unix/Linux Programming-事件驱动编程:编写一个视频游戏
  12. 2台电脑一根网线传文件_一根网线做两根用?接两个水晶头?是的你没看错
  13. CherryTree:一款免费开源的富文本笔记软件
  14. 树莓派教程 - 2.1 树莓派USB摄像头 树莓派罗技免驱摄像头 fswebcam常用参数
  15. MHDD检测不到硬盘的解决办法
  16. 腾讯、美团通报反腐情况;马斯克回应:涨价也没人补差价;滴滴出行恢复新用户注册 | EA周报...
  17. Java多线程 信号量和屏障实现控制并发线程数量,主线程等待所有线程执行完毕2
  18. ROS项目库依赖库 CMakeLists.txt中添加第三方库路径
  19. 微分方程数值解法(PID仿真用一阶被控对象库PLC算法实现)
  20. 小丸子学Redis系列之——Data types(一)

热门文章

  1. Spring cloud(Finchley)微服务框架,sleuth整合zipkin链路追踪失效的问题
  2. 【小白学习Keras教程】四、Keras基于数字数据集建立基础的CNN模型
  3. 十九、深入Python匿名函数
  4. python 学习 我推荐这本书,适合特别没有程序基础或者编程思维较差的人,
  5. 直播预告 | AAAI 2022论文解读:基于锚框排序的目标检测知识蒸馏
  6. 大规模图训练调优指南
  7. 图数据的攻与防:智谱AI和biendata联合组织KDD Cup 2020
  8. f-GAN简介:GAN模型的生产车间
  9. NeurIPS 2019 | 用于弱监督图像语义分割的新型损失函数
  10. 近期有哪些值得读的QA论文?| 专题论文解读