SQL中的组合优化匹配

时间:2012-12-13 10:48:23

标签: sql algorithm db2 combinatorics

我在SQL中开发匹配算法时遇到问题。我有一张桌子subjects。这些中的每一个都需要与表controls中相同数量的行匹配(为了这个问题,我们需要为每个主题选择两行或控件)。所选控件的位置必须完全匹配,并且所选控件的值应match_field尽可能接近主题。

以下是一些示例数据:

表科目:

id   location    match_field
1    1           190
2    2           2000
3    1           100

表格控件:

id   location    match_field
17    1          70
11    1          180
12    1          220
13    1          240
14    1          500
15    1          600
16    1          600
10    2          30
78    2          1840
79    2          2250

以下是样本数据的最佳结果:

subject_id control_id  location    match_field_diff
1          12          1           30
1          13          1           50
2          78          2           160
2          79          2           250
3          17          1           30
3          11          1           80

它变得棘手,因为,例如,控件11是与主题1最接近的匹配。但是,在最佳解决方案中,控件11与主题3匹配。

我相信Hungarian Algorithm接近这个问题的“正确”解决方案。但是,没有相同数量的主题和控制,也不会使用所有控件(我有几千个主题和几百万个潜在控件)。

没有必要获得绝对最佳结果;一个非常好的近似对我来说没问题。

似乎应该有一个很好的基于集合的解决方案来解决这个问题,但我想不出怎么做。以下是一些代码,仅根据位置为每个主题分配相同数量的控件:

select * from (
    select   subject.id, 
             control.id,
             subject.location,
             row_number() over (
                 partition by subject.location
                 order by subject.id, control.id
             ) as rn,
             count(distinct control.id)     over (
                 partition by subject.location
             ) as controls_in_loc
         from subjects
         join controls on control.location = subject.location
    )
    where mod(rn,controls_in_loc+1) = 1

但是,我无法弄清楚如何添加模糊匹配组件。我正在使用DB2,但如果您使用其他东西,可以将算法转换为DB2。

提前感谢您的帮助!

更新:我基本上确信SQL不适合这项工作。但是,为了确保(并且因为它是一个有趣的问题),我提供了一个赏金,看看是否可以使用有效的SQL解决方案。它需要是一个基于集合的解决方案。它可以使用迭代(多次循环同一个查询以实现结果),但迭代次数需要远远小于大型表的行数。它不应该循环遍历表中的每个元素或使用游标。

2 个答案:

答案 0 :(得分:5)

虽然匈牙利算法可以运行,但在您的情况下可以使用更简单的算法。隐式成本矩阵是一种特殊形式的对称矩阵:

ABS(SUBJ.match_field-CTRL.match_field)

因此,您可以相对轻松地证明在{{1>}排序的最佳作业 {SUBJ i ,CTRL j } 我们也会订购SUBJ.match_field的价值。

证明: 考虑作业 {SUBJ i ,CTRL j } CTRL.match_field订购的SUBJ.match_field未订购。然后你至少有一个反转,即一对分配 {SUBJ i1 ,CTRL j1 } 和< em> {SUBJ i2 ,CTRL j2 } 这样

CTRL.match_field i1 &lt; SUBJ.match_field i2 ,但

SUBJ.match_field j1 &gt; CTRL.match_field <子> J2

然后你可以用一个非倒置的

替换倒置的对

{SUBJ i1 ,CTRL j2 } {SUBJ i2 ,CTRL J1 }

的成本小于或等于CTRL.match_field i1 i2 )的所有六个相对展示位置的反向分配的成本SUBJ.match_field j1 j2 )(link to Wolfram Alpha)。的:证明

有了这个观察结果,很容易证明下面的dynamic programming算法提出了最佳分配:

  • 为每个主题制作CTRL.match_field个副本;按N
  • 排序
  • match_field
  • 排序控件
  • 准备一个大小为match_field
  • 的空数组assignments
  • memoizationN * subject.SIZE准备一个大小为mem的空二维数组N * subject.SIZE;将所有元素设置为control.SIZE
  • 调用下面伪代码中定义的-1
  • Recursive_Assign表格现在包含assignments Ni位置N*i的{​​{1}}分配,N*(i+1)FUNCTION Recursive_Assign // subjects contains each original subj repeated N times PARAM subjects : array of int[subjectLength] PARAM controls: array of int[controlLength] PARAM mem : array of int[subjectLength,controlLength] PARAM sp : int // current subject position PARAM cp : int // current control position PARAM assign : array of int[subjectLength] BEGIN IF sp == subjects.Length THEN RETURN 0 ENDIF IF mem[sp, cp] > 0 THEN RETURN mem[sp, cp] ENDIF int res = ABS(subjects[sp] - controls[cp]) + Recursive_Assign(subjects, controls, mem, sp + 1, cp + 1, assign) assign[sp] = cp IF cp+1+subjects.Length-sp < controls.Length THEN int alt = Recursive_Assign(subjects, controls, mem, sp, cp + 1, assign) IF alt < res THEN res = alt ELSE assign[sp] = cp ENDIF ENDIF RETURN (mem[sp, cp] = res) END CREATE TYPE SubjTableType AS TABLE (row int, id int, match_field int) CREATE TYPE ControlTableType AS TABLE (row int, id int, match_field int) CREATE PROCEDURE RecAssign ( @subjects SubjTableType READONLY , @controls ControlTableType READONLY , @sp int , @cp int , @subjCount int , @ctrlCount int ) AS BEGIN IF @sp = @subjCount BEGIN RETURN 0 END IF 1 = (SELECT COUNT(1) FROM #MemoTable WHERE sRow=@sp AND cRow=@cp) BEGIN RETURN (SELECT best FROM #MemoTable WHERE sRow=@sp AND cRow=@cp) END DECLARE @res int, @spNext int, @cpNext int, @prelim int, @alt int, @diff int, @sId int, @cId int SET @spNext = @sp + 1 SET @cpNext = @cp + 1 SET @sId = (SELECT id FROM @subjects WHERE row = @sp) SET @cId = (SELECT id FROM @controls WHERE row = @cp) EXEC @prelim = RecAssign @subjects=@subjects, @controls=@controls, @sp=@spNext, @cp=@cpNext, @subjCount=@subjCount, @ctrlCount=@ctrlCount SET @diff = ABS((SELECT match_field FROM @subjects WHERE row=@sp)-(SELECT match_field FROM @controls WHERE row=@cp)) SET @res = @prelim + @diff IF 1 = (SELECT COUNT(1) FROM #Assignments WHERE sRow=@sp) BEGIN UPDATE #Assignments SET cId=@cId, sId=@sId, diff=@diff WHERE sRow=@sp END ELSE BEGIN INSERT INTO #Assignments(sRow, sId, cId, diff) VALUES (@sp, @sId, @cId, @diff) END IF @cp+1+@subjCount-@sp < @ctrlCount BEGIN EXEC @alt = RecAssign @subjects=@subjects, @controls=@controls, @sp=@sp, @cp=@cpNext, @subjCount=@subjCount, @ctrlCount=@ctrlCount IF @alt < @res BEGIN SET @res = @alt END ELSE BEGIN UPDATE #Assignments SET cId=@cId, sId=@sId, diff=@diff WHERE sRow=@sp END END INSERT INTO #MemoTable (sRow, cRow, best) VALUES (@sp, @cp, @res) RETURN @res END

-- The procedure uses a temporary table for memoization:
CREATE TABLE #MemoTable (sRow int, cRow int, best int)
-- The procedure returns a table with assignments:
CREATE TABLE #Assignments (sRow int, sId int, cId int, diff int)

DECLARE @subj as SubjTableType
INSERT INTO @SUBJ (row, id, match_field) SELECT ROW_NUMBER() OVER(ORDER BY match_field ASC)-1 AS row, id, match_field FROM subjects
DECLARE @ctrl as ControlTableType
INSERT INTO @ctrl (row, id, match_field) SELECT ROW_NUMBER() OVER(ORDER BY match_field ASC)-1 AS row, id, match_field FROM controls
DECLARE @subjCount int
SET @subjCount = (SELECT COUNT(1) FROM subjects)
DECLARE @ctrlCount int
SET @ctrlCount = (SELECT COUNT(1) FROM controls)
DECLARE @best int
EXEC @best = RecAssign
    @subjects=@subj
,   @controls=@ctrl
,   @sp=0
,   @cp=0
,   @subjCount=@subjCount
,   @ctrlCount=@ctrlCount
SELECT @best
SELECT sId, cId, diff FROM #Assignments

Here is an implementation of the above pseudocode using C# on ideone.

此算法已准备好在SQL中以基于集合的方式重写。试图将其纳入原始问题设置(按位置分组并制作主题的多个副本)会给已经相当复杂的过程增加不必要的复杂层,因此我将通过使用表来简化相当多的事情SQL Server的评估参数。我不确定DB2是否提供类似的功能,但如果没有,您应该能够用临时表替换它们。

下面的存储过程几乎直接将上述伪代码转录为SQL Server的存储过程语法:

subjects

以下是调用此存储过程的方法:

controls

上述调用假设Nsubjects已按位置过滤,{{1}}个{{1}}副本已插入表值参数(或者在进行调用之前的DB2中的临时表。

这是a running demo on sqlfiddle

答案 1 :(得分:2)

我建议你看一下maximum matchings in a bipartite graph的问题和算法。我们的想法是构建一个图表,其中左边的节点是你的subjects,右边的节点是controls(这就是为什么叫做bipartite)。构建图表非常简单,您可以创建一个连接到所有主题的source节点,并将所有控制节点连接到sink节点。然后,如果适用,您可以在subjectcontrol节点之间创建边缘。然后运行最大匹配算法,它将为您提供所需的内容,主题和控件的最大匹配。

确保结帐this Boost BGL example如何操作,您只需构建图表并调用BGL函数edmonds_maximum_cardinality_matching