数据关联或者数据匹配的方法有:匈牙利算法、最近邻关联算法、回溯法等,当然还有很多其他的算法~
一、匈牙利算法:是一种在多项式时间内求解任务分配问题的组合优化算法。
基本概念如下:
(1)寻找交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边...形成的路径叫交替路。
(2)寻找增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。
匈牙利算法就是在不断寻找增广路,如果找不到增广路,就说明达到了最大匹配。
举个栗子:参考匈牙利算法(二分图) - 神犇(shenben) - 博客园
1.起始未匹配(匹配都从同一向的点开始,如x的点)
2.由x1开始寻找匹配
3.再到x2寻找匹配
4.到x3进行匹配。但是x3可以匹配的第一个点被x1占用了,此时先寻找x3的交替路x3-y1-x1-y4,把交替路中原来已经匹配的x1y1的取消。改为x1与y4匹配
5.同理,寻找后续未进行匹配的点。
匈牙利可以使用DFS(深度优先搜索)和BFS(广度优先搜索)来实现,上述例子使用的DFS,寻找到的是最大匹配。匈牙利还可以找到最优匹配、最小点覆盖等,还有带权重的KM算法。此处不作赘述。
匈牙利DFS代码实现(此处使用递归实现,也可以使用栈实现):参考算法学习笔记(5):匈牙利算法 - 知乎
int M, N; //M, N分别表示左、右侧集合的元素数量
int Map[MAXM][MAXN]; //邻接矩阵存图
int p[MAXN]; //记录当前右侧元素所对应的左侧元素
bool vis[MAXN]; //记录右侧元素是否已被访问过
bool match(int i)
{
for (int j = 1; j <= N; ++j)
if (Map[i][j] && !vis[j]) //有边且未访问
{
vis[j] = true; //记录状态为访问过
if (p[j] == 0 || match(p[j])) //如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
{
p[j] = i; //当前左侧元素成为当前右侧元素的新匹配
return true; //返回匹配成功
}
}
return false; //循环结束,仍未找到匹配,返回匹配失败
}
int Hungarian()
{
int cnt = 0;
for (int i = 1; i <= M; ++i)
{
memset(vis, 0, sizeof(vis)); //重置vis数组,每进行一次搜索都要重置
if (match(i))
cnt++;//记录匹配上的数目
}
return cnt;
}
小结:此处的匈牙利DFS是为了找到最大匹配,如果已经找到了最大匹配就会跳出递归,即使还会有其他的匹配方式也不会再寻找,默认使用当前找到匹配方式。虽然匈牙利DFS找到了最大的匹配,但不一定是最优的匹配方式,所以还可以结合其他的算法得到最优的匹配方式,后续讲匈牙利DFS与全局最近邻关联(GNN)算法的结合得到最优匹配。当然匈牙利KM算法也可以得到最优匹配。
二、DFS(深度优先搜索算法):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。
DFS的基本代码模板如下:参考C语言 DFS(深度优先搜索算法) 详解_伏城无嗔的博客-CSDN博客_c语言dfs算法
int check(参数)
{
if(满足条件)
return 1;
return 0;
}
void dfs(int step)
{
判断边界
{
相应操作
}
尝试每一种可能
{
满足check条件
标记
继续下一步dfs(step+1)
恢复初始状态(回溯的时候要用到)
}
}
小结:此处DFS也是采用递归实现的,与匈牙利的DFS类似,不同之处是匈牙利DFS只寻找到一种最大匹配就会结束寻找,此时的最大匹配不一定是最优的,也有可能是最优的;而DFS则会遍历各种匹配方式,但是如果不加入其他的条件加以辅助找到最优的匹配,那么它将输出最后一种匹配方式。所以加入全局最近邻关联(GNN)算法使得从DFS的各种匹配中找到最优的匹配。
三、DFS与全局最近邻算法(GNN)
- 首先是使用DFS寻找各种匹配方式(不是匈牙利DFS),记录各个匹配对
- 然后根据各个匹配对的关联度来是否是最优匹配,选择最优匹配的前提可以是当前的匹配是最大的匹配
- 最后将最优匹配的匹配对输出
四、递归
参考:关于递归前后语句执行顺序_HoryC的博客-CSDN博客_递归代码执行顺序
解释循环中的递归调用_冰凌其的博客-CSDN博客_循环递归调用
小结:递归前后都有代码,且递归位于循环中:先执行位于递归前的代码,然后将当前循环中位于递归后的代码先压栈,再将后面循环的代码压栈;当递归到出口后,先执行最后压栈的代码,再执行前面压栈的代码,秉承先进后出原则继续进行递归。