java将list拆分为三元组_关于Java:三元组的最佳合并

我正在尝试针对以下问题提出一种算法:

我有一个整数三元组的集合-我们将这些整数称为A,B,C。内部存储的值可能很大,因此通常无法创建大小为A,B或C的数组。目标是最小化集合的大小。为此,我们提供了一条简单的规则,允许我们合并三胞胎:

对于两个三元组(A,B,C)和(A',B',C'),如果B == B'并且C = C,则移开原始的三元组并放置三元组(A | A',B,C)。 ',其中|是按位或。 B和C也适用类似的规则。

换句话说,如果两个三胞胎的两个值相等,则按位或第三个值删除这两个三胞胎,并将结果放入集合中。

在类似的情况下,贪婪的方法通常会产生误导,因此也正是这个问题,但是我找不到简单的反例来得出正确的解决方案。对于250个项目的正确解为14的列表,通过贪婪合并计算得出的平均大小约为30(从20到70)。随着列表大小的增加,次优开销也会变大。

我也尝试过设置位计数,但是没有发现有意义的结果。显而易见的事实是,如果记录是唯一的(可以假定),则设置的位数总是会增加。

这是愚蠢的贪婪实现(这只是概念上的事情,请不要考虑代码风格):

public class Record {

long A;

long B;

long C;

public static void main(String[] args) {

List data = new ArrayList<>();

// Fill it with some data

boolean found;

do {

found = false;

outer:

for (int i = 0; i < data.size(); ++i) {

for (int j = i+1; j < data.size(); ++j) {

try {

Record r = merge(data.get(i), data.get(j));

found = true;

data.remove(j);

data.remove(i);

data.add(r);

break outer;

} catch (IllegalArgumentException ignored) {

}

}

}

} while (found);

}

public static Record merge(Record r1, Record r2) {

if (r1.A == r2.A && r1.B == r2.B) {

Record r = new Record();

r.A = r1.A;

r.B = r1.B;

r.C = r1.C | r2.C;

return r;

}

if (r1.A == r2.A && r1.C == r2.C) {

Record r = new Record();

r.A = r1.A;

r.B = r1.B | r2.B;

r.C = r1.C;

return r;

}

if (r1.B == r2.B && r1.C == r2.C) {

Record r = new Record();

r.A = r1.A | r2.A;

r.B = r1.B;

r.C = r1.C;

return r;

}

throw new IllegalArgumentException("Unable to merge these two records!");

}

您有解决该问题的想法吗?

"贪婪的方法"……到底是什么?

找到一些符合条件的记录,将它们合并,放回去,然后在有一些记录时重复。

令我担心的是:假设您有(A,B,C),(A,B,C)和(A,B,C)。有两种可能的合并(A | A,B,C)或(A,B | B,C)。如果应用其中一个合并,则其他合并将变得不可能。是这样吗?

对,就是这样。如果一直只有一种方法,那将是微不足道的。

还不清楚你想要什么。您是否需要内存中的数据结构?还是您需要一种将数据存储在文件中的方法?

我需要算法来找到最佳的合并方式,以使集合的最终大小最小,即它包含的记录越少越好。

您是否尝试过实验(例如以随机顺序进行贪婪合并)以了解与贪婪解决方案相比,使用最佳解决方案可以获得多少收益?

是的,我做了一些工作,已经用更多信息更新了问题。

有趣的问题,除了强力深度优先搜索之外,我真的没有任何线索。我试着贪婪地遵循以下规则:如果X和Y的合并可以立即与某事物合并,则将X和Y合并。如果这些都不存在,则X和Y的合并与其他任何三元组有一个共同的位置,合并X和Y。如果失败,则合并任何内容。这比我预期的要差。

整数是32位,并且您有64位cpu吗?

进行多次随机合并(例如1000次)并保持最佳状态的效果如何?

@BasBrekelmans:整数是64位,并且64位CPU可用。

这可能是一个很大的问题,因为决策树可能会很大。如果原始集合中有N个可能的合并,则选择其中一个将为第二步留下至少N-1个可能的合并。但也许更多。甚至在某些病理情况下,您永远都无法达到0的合并可能性-但我不确定这一点...在一般情况下找到精确的解决方案很难,而且您可能只需要对启发式近似感到满意。我可能是错的-不会是第一次...

我可能是错的,但我感觉到此问题具有非本地行为,这意味着通过允许级联合并,合并顺序的细微更改会对其他地方产生重大影响。如果为真,这可能会使解决方案的逐步完善无效(模拟退火,遗传优化...)。

@YvesDaoust:不理想,但没有我想的那么糟糕。我之前使用过相同的数据,将其复制粘贴四次,并对其A进行了少许修改。这种模式在我需要整件事的数据中非常常见。由于理想的解决方案仍然是14(对于1000个项目),因此在1000次尝试中发现的最佳解决方案是38。对于500个项目,它是37。可扩展性比我想象的好。有了一些索引,这可能会很有用。输入数据的大小预计为10 ^ 4-10 ^ 5

@twalberg:该算法将始终终止,因为每次合并都会将集合的长度减少1。因此,在理想情况下,我们仅以集合中的一条记录结束。

更深入的洞察力:您的数据惊人地压缩。确实,在随机的64位数据的三元组中,实际上没有一个具有两个相等的值,并且根本不应该进行压缩。因此,这些数字之间具有很强的相关性。也许您应该在三元组的生成过程中更接近地重述您的问题。

更多见解:也许数据本身可以压缩。我的意思是,当以二进制表示形式查看值时,可能会出现某种模式(例如某些位始终为0或始终成对为1s)。您可以查看出现的不同值的数量。为此,还应考虑由成对或与组成的所有组合。

更多见解:您可以看一下"压缩森林"。我的意思是,如果您考虑将两个三元组全部合并为一个新的三元组,则可以形成一个由二叉树组成的图。也许您可以从这片森林的形状或统计信息(例如树的深度,平均分支因子...)中学习,以获得最佳解决方案。

更多见解:我建议采用以下启发式方法。 1)使用贪婪的合并策略,但首先合并具有最小汉明距离的三元组。 2)相同,但汉明距离最大。 [基本原理是尝试从按位关联中受益。]

顺便说一句,您如何知道500个三元组问题的最佳解决方案?

您能告诉我们一些背后的故事吗?一些环境可能会导致一些新见解。

@harold Im及时给出了很多事件,通常可以找到某种模式。目的是找到模式。整数中的每个位代表"事件发生在该特定的年/月/日"。例如,2014年3月7日的表示形式为[1 << (2014-1970), 1 << 3, 1 << 7]。上面描述的模式允许我们压缩这些事件,以便可以说该事件在2000-2010年间每发生1次。

好吧,那我可以建议另一个想法吗?将模式表示为NFA,可以精确地接受该日期集(易于构建),转换为DFA并将其最小化。这也是一种具有最坏情况指数行为的算法,但是它应该"通常很快",而不是像蛮力一样"总是很糟糕"。

@harold问题是我不知道该模式。我需要以某种方式猜测-这是算法的结果。

@ Danstahr是的,那就是我的建议。生成的DFA将描述该模式。您构建的NFA是微不足道的,只是一堆琐碎的NFA的取舍,每个NFA都接受一个特定的日期。

@harold我使用brics.dk/automaton进行了尝试,它给出的效果比10最佳的天真的实现运行效果差。

您是否可以添加一些可以正常工作的最小示例代码来显示您当前正在使用的算法?

@ Danstahr我不明白,这不是最佳选择吗?

@Flovdis问题已更新。

@Danstahr:根据您的问题描述,您应该查看以下SO答案(如果您尚未这样做的话):stackoverflow.com/a/4202095/44522和stackoverflow.com/a/3251229/44522

我认为NP难题3D匹配可以减少多时制。

@Danstahr能否为我们提供250个值的输入,结果为14?我已经编写了一些代码,并想对其进行测试。

这将是一个很长的答案,可惜没有最佳解决方案(对不起)。但是,这是将贪心问题解决方案应用于您的问题的认真尝试,因此从原理上讲可能很有用。我没有实现讨论的最后一种方法,也许该方法可以产生最佳解决方案-尽管我不能保证。

好。

0级:不是很贪心

根据定义,贪婪算法具有以局部最优的方式(即,当前最优)选择下一步骤的试探法,希望达到总有可能或不可能的全局最优。

好。

您的算法选择任何可合并的对并将其合并,然后继续进行。它不评估此合并的含义以及是否有更好的本地解决方案。因此,我完全不会称您的方法贪婪。这只是一种解决方案,一种方法。我将其称为盲算法,以便我可以在回答中简洁地引用它。我还将使用算法的稍作修改的版本,该算法不会删除两个三元组并附加合并的三元组,而只会删除第二个三元组,并用合并的三元组替换第一个三元组。生成的三元组的顺序不同,因此最终结果也可能不同。让我对代表性数据集运行此修改后的算法,并用*标记要合并的三元组:

好。

0: 3 2 3   3 2 3   3 2 3

1: 0 1 0*  0 1 2   0 1 2

2: 1 2 0   1 2 0*  1 2 1

3: 0 1 2*

4: 1 2 1   1 2 1*

5: 0 2 0   0 2 0   0 2 0

Result: 4

1级:贪婪

要使用贪婪算法,您需要以允许比较多个可用选项的方式来制定合并决策。对我来说,合并决策的直观表达是:

好。

If I merge these two triplets, will the resulting set have the maximum possible number of mergable triplets, when compared to the result of merging any other two triplets from the current set?

Ok.

我再说一遍,这对我来说很直观。我没有证据表明这会导致全局最优解,甚至不能证明它会比盲目算法产生更好或更平等的解决方案,但它符合贪婪的定义(并且很容易实现)。让我们在上面的数据集上进行尝试,显示每个步骤之间的可能合并(通过指示三元组对的索引)以及每个可能合并的可合并数量:

好。

mergables

0: 3 2 3  (1,3)->2

1: 0 1 0  (1,5)->1

2: 1 2 0  (2,4)->2

3: 0 1 2  (2,5)->2

4: 1 2 1

5: 0 2 0

除了合并三元组1和5以外的任何选择都是可以的,如果我们采用第一对,我们将获得与盲算法相同的过渡集(我这次将折叠指数以消除缺口):

好。

mergables

0: 3 2 3  (2,3)->0

1: 0 1 2  (2,4)->1

2: 1 2 0

3: 1 2 1

4: 0 2 0

这是该算法获得不同结果的地方:之所以选择三元组2和4,是因为与盲算法做出的选择相比,合并后仍然存在一个合并:

好。

mergables

0: 3 2 3  (2,3)->0   3 2 3

1: 0 1 2             0 1 2

2: 1 2 0             1 2 1

3: 1 2 1

Result: 3

第2级:非常贪婪

现在,这一直观启发式方法的第二步是进一步展望合并,然后提出启发式问题。概括地说,您会期望k进一步合并,并应用上述启发式方法,回??溯并确定最佳选项。到现在为止,这变得非常冗长,因此,举例来说,我将以超前方式执行这一新的启发式方法的第一步:

好。

mergables

0: 3 2 3  (1,3)->(2,3)->0

1: 0 1 0         (2,4)->1*

2: 1 2 0  (1,5)->(2,4)->0

3: 0 1 2  (2,4)->(1,3)->0

4: 1 2 1         (1,4)->0

5: 0 2 0  (2,5)->(1,3)->1*

(2,4)->1*

应用此新的启发式方法时,标有星号的合并序列是最佳选择。

好。

如果需要口头说明:

好。

Instead of checking how many merges are possible after each possible merge for the starting set; this time we check how many merges are possible after each possible merge for each resulting set after each possible merge for the starting set. And this is for lookahead 1. For lookahead n, you'd be seeing a very long sentence repeating the part after each possible merge for each resulting set n times.

Ok.

第3级:让我们削减贪婪

如果仔细观察,即使适度的输入和前瞻(*),以前的方法也会带来灾难性的后果。对于超过20个三元组的输入,超出4个合并前瞻的时间将花费不合理的时间。这里的想法是切出似乎比现有解决方案差的合并路径。如果我们要执行超前10次,并且特定的合并路径在3次合并后产生的可合并量比5次合并后的另一条路径少,那么我们可能会削减当前合并路径并尝试另一条合并路径。这应该节省大量时间,并允许进行大前瞻,这有望使我们更接近于全局最优解决方案。我还没有实现这一测试。

好。

(*): Assuming a large reduction of input sets is possible, the number of merges is

proportional to input size, and

lookahead approximately indicates how much you permute those merges.

So you have choose lookahead from |input|, which is

the binomial coefficient that for lookahead ? |input| can be approximated as

O(|input|^lookahead) -- which is also (rightfully) written as you are thoroughly screwed.

Ok.

放在一起

这个问题让我很感兴趣,于是我坐下来用Python编写了代码。可悲的是,我能够证明不同的前瞻可能会产生不同的结果,甚至盲目算法有时也会比前瞻1或2更好。这直接证明了解决方案不是最优的(至少对于lookahead ? |input|) 。请参阅源代码和帮助程序脚本,以及github上的三重证明。请注意,除了合并结果的记忆外,我没有尝试在CPU周期上优化代码。

好。

好。

谢谢您的回答。 我接受它对我的帮助最大。 最后,我在数据中手动找到了一些重复的模式,首先将它们合并,然后使用"最好的x次随机幼稚通行证"方法。 运作良好。

只是为了使这篇文章保持最新,我们证明了原始问题是NP难题。 二分维问题是最初发布的问题的一个小案例,并且是NP本身完整的问题。

h ...我无法将给定的问题与众所周知的np完全问题相提并论。 :)

根据您的问题描述:

I'm given a bunch of events in time that's usually got some pattern.

The goal is to find the pattern. Each of the bits in the integer

represents"the event occurred in this particular year/month/day". For

example, the representation of March 7, 2014 would be [1 <<

(2014-1970), 1 << 3, 1 << 7]. The pattern described above allows us to

compress these events so that we can say 'the event occurred every 1st

in years 2000-2010'. – Danstahr Mar 7 at 10:56

我想用MicSim指出的答案来鼓励您,特别是

Based on your problem description, you should check out this SO

answers (if you didn't do it already):

stackoverflow.com/a/4202095/44522 and

stackoverflow.com/a/3251229/44522 – MicSim Mar 7 at 15:31

与使用的方法相比,目标的描述要清晰得多。我很害怕合并的想法将一无所获。听起来吓人。您得到的答案取决于您处理数据的顺序。你不要那样

看来您需要保留数据并进行汇总。因此,您可以尝试计算这些位,而不是合并它们。当然,请尝试聚类算法,但更具体地说,请尝试进行回归分析。我认为,如果您创建一些辅助数据,使用相关性分析会得到很好的结果。例如,如果您创建"星期一","星期二","每月的第一个星期一","每月的第一个星期二",..."每月的第二个星期一",..."偶数年"的数据,"每四年"," le年","无leap日的年",……"以3结尾的年",……

您现在所拥有的是"每月的第一天","每月的第二天",……"一年的第一个月","一年的第二个月",……这些听起来像足够复杂的描述来找到模式。

如果您认为有必要继续执行已开始的方法,则可以将其视为搜索而非合并。我的意思是,您需要成功的标准/衡量标准。您可以对原始数据进行合并,同时严格要求A == A'。然后在要求B == B'的同时对原始数据重复合并。同样,C == C'。最后比较结果(使用标准/度量)。你知道这是怎么回事吗?您对位计数的想法可以用作度量。

还有一点,您可以在性能上做得更好。我建议您不要遍历所有数据并匹配成对的数据,而应单遍处理数据并将其分类到箱中。 HashMap是您的朋友。确保同时实现hashCode()和equals()。使用地图,您可以按键对数据进行排序(例如,月份和日期都匹配的位置),然后在值中累加年份。哦,伙计,这可能是很多编码。

最后,如果执行时间不是问题,并且您不需要性能,那么可以尝试一下。您的算法取决于数据的顺序。您会根据不同的排序得到不同的答案。您成功的标准是合并后以最小的规模获得答案。因此,反复循环使用此算法:洗净原始数据,进行合并,保存结果。现在,每次循环都保留迄今为止最小的结果。每当得到的结果小于先前的最小值时,请打印出迭代次数和大小。这是一种非常简单的算法,但是如果有足够的时间,它将找到较小的解决方案。根据您的数据大小,可能需要太长时间...

亲切的问候,

-约翰·斯托什

我没有解决方案,但是有一些想法。

表示

问题的一种有用的可视表示形式是将三元组视为3D空间的点。您有整数,因此记录将是网格的节点。并且,当且仅当表示它们的节点位于同一轴上时,两个记录才可合并。

反例

我发现了一个(最小)示例,其中贪婪算法可能会失败。请考虑以下记录:

(1, 1, 1)   \

(2, 1, 1)   |     (3, 1, 1)  \

(1, 2, 1)   |==>  (3, 2, 1)  |==> (3, 3, 1)

(2, 2, 1)   |     (2, 2, 2)  /    (2, 2, 2)

(2, 2, 2)   /

但是,如果选择错误的方式,它可能会卡在三个记录中:

(1, 1, 1)   \

(2, 1, 1)   |     (3, 1, 1)

(1, 2, 1)   |==>  (1, 2, 1)

(2, 2, 1)   |     (2, 2, 3)

(2, 2, 2)   /

直觉

我觉得这个问题有点类似于在图中找到最大匹配。这些算法中的大多数算法都是从任意次优解决方案开始找到最佳解决方案的,然后通过搜索具有以下属性的扩充路径,使其在每次迭代中都变得"更加优化":

它们很容易找到(节点数中的多项式时间),

一条扩展之路,当前的解决方案可以设计成一种新的解决方案,它比当前的解决方案要好得多,

如果找不到扩展路径,则当前解决方案为最佳。

我认为,以类似的精神可以找到问题的最佳解决方案。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值