我有一份钥匙清单:
['A', 'B', 'C']
对于每个键,都有一个属性列表:
{
'A': [2,3],
'B': [1,2],
'C': [4]
}
我希望对标签列表进行排序,以便相邻标签共享尽可能多的属性
在上面的示例中A
和B
共享关系2
,因此它们应该彼此相邻-而C
与它们不共享任何内容,因此它可以去任何地方
因此,本例的可能顺序如下:
["A","B","C"] # acceptable
["A","C","B"] # NOT acceptable
["B","A","C"] # acceptable
["B","C","A"] # NOT acceptable
["C","A","B"] # acceptable
["C","B","A"] # acceptable
事实上,我更希望通过将它们放入“桶”来表示:
[["A", "B"], ["C"]] # this can represent all four possible orders above.
但是,如果标签属于两个不同的存储桶,则会出现问题:
{
'A': [2,3],
'B': [1,2],
'C': [1,4]
}
我将如何陈述这一点
我可以这样说:
[["A", "B"], ["C", "B"]]
但我需要另一个处理步骤来翻转桶列表 在最后陈述中:
["A", "B", "C"]
除此之外,还可能存在递归嵌套的bucket:
[[["A","B"], ["C"]], ["D"]]
然后这些可能重叠:
[[["A","B"], ["C"]], ["A","D"]]
“接近度”,即解的质量定义为相邻关系的交集之和(质量越高越好):
def measurequality(result,mapping):
lastKey = None
quality = 0
for key in result:
if lastKey is None:
lastKey = key
continue
quality += len(set(mapping[key]).intersection(mapping[lastKey]))
lastKey = key
return quality
# Example determining that the solution ['A', 'B', 'C'] has quality 1:
#measurequality(['A', 'B', 'C'],
# {
# 'A': [2,3],
# 'B': [1,2],
# 'C': [4]
# })
暴力强制并不构成解决方案(实际上,列表包含数千个元素——不过,如果有人得到了比O(n²)
更好的暴力强制方法……)
但是,使用暴力强制创建额外的测试用例是可能的:
L
个n
项的列表['A','B','C',...]
R
(在0
和n
之间最多有n
个随机数就足够了)李>L
排列,并将它们与R
一起馈送到measurequality()
中,并保留那些具有最大返回值的排列(可能不是唯一的)李>用于创建随机测试用例以测试实现的代码:
import string
import random
def randomtestcase(n):
keys=list(string.ascii_uppercase[0:n])
minq = 0
maxq = 0
while minq == maxq:
items={}
for key in keys:
items[key] = random.sample(range(1,10),int(random.random()*10))
minq = n*n
minl = list(keys)
maxq = 0
maxl = list(keys)
for _ in range(0, 1000): # TODO: explicitly construct all possible permutations of keys.
random.shuffle(keys)
q = measurequality(keys,items)
if q < minq:
minq = q
minl = list(keys)
if maxq < q:
maxq = q
maxl = list(keys)
return ( items, minl, maxq )
( items, keys, quality ) = randomtestcase(5)
sortedkeys = dosomething( keys, items )
actualquality = measurequality( sortedkeys, items )
if actualquality < quality:
print('Suboptimal: quality {0} < {1}'.format(actualquality,quality))
许多“解决方案”中不起作用的一个(非常糟糕,这一个没有初始元素的选择/在结果列表的前置和追加之间的选择,我在其他列表中有):
def dosomething(keys,items):
result = []
todo = list(keys)
result.append(todo.pop())
while any(todo):
lastItems = set(items[result[-1]])
bestScore = None
bestKey = None
for key in todo:
score = set(items[key]).intersection(lastItems)
if bestScore is None or bestScore < score:
bestScore = score
bestKey = key
todo.remove(bestKey)
result.append(bestKey)
return result
(还可以查看上文暴力强制部分中的示例生成器。)
测试代码尝试以下示例:
def test(description,acceptable,keys,arguments):
actual = dosomething(keys,arguments)
if "".join(actual) in acceptable:
return 0
print("\n[{0}] {1}".format("".join(keys),description))
print("Expected: {0}\nBut was: {1}".format(acceptable,actual))
print("Quality of result: {0}".format(measurequality(actual,arguments)))
print("Quality of expected: {0}".format([measurequality(a,arguments) for a in acceptable]))
return 1
print("EXAMPLES")
failures = 0
# Need to try each possible ordering of letters to ensure that the order of keys
# wasn't accidentially already a valid ordering.
for keys in [
["A","B","C"],
["A","C","B"],
["B","A","C"],
["B","C","A"],
["C","A","B"],
["C","B","A"]
]:
failures += test(
"1. A and B both have 2, C doesn't, so C can go first or last but not in between.",
["ABC", "BAC", "CAB", "CBA"],
keys,
{
"A": [2,3],
"B": [1,2],
"C": [4]
})
failures += test(
"2. They all have 2, so they can show up in any order.",
["ABC", "ACB", "BAC", "BCA", "CAB", "CBA"],
keys,
{
"A": [2,3],
"B": [1,2],
"C": [2]
})
failures += test(
"3. A and B share 2, B and C share 1, so B must be in the middle.",
["ABC", "CBA"],
keys,
{
"A": [2,3],
"B": [1,2],
"C": [1]
})
failures += test(
"4. Each shares something with each other, creating a cycle, so they can show up in any order.",
["ABC", "ACB", "BAC", "BCA", "CAB", "CBA"],
keys,
{
"A": [2,3],
"B": [1,2],
"C": [1,3]
})
if 0 < failures:
print("{0} FAILURES".format(failures))
正如有人问的那样:用于关系的数字没有优先顺序。存在一个优先顺序,但它是一个偏序,而不是一个数字。我只是没有提到它,因为它让问题变得更难
举个例子:
{
'A': [2,3],
'B': [1,2],
'C': [4]
}
可替换为以下内容(使用字母而不是数字,并添加优先级信息):
{
'A': [('Q',7),('R',5)],
'B': [('P',6),('Q',6)],
'C': [('S',5)]
}
注意
这是一个Travelling Salesman Problem,一个众所周知的难以优化解决的问题。给出的代码在大约15分钟内解决了10000个简单互连节点(即每个节点有一个或两个关系)的问题。对于互连更为丰富的序列,它的性能较差。下面的测试结果对此进行了探讨
OP提到的优先权的概念没有被探讨
给出的代码包括一个heuristic解决方案、一个对大型
node_set
来说是最佳但不实用的蛮力解决方案,以及一些简单但可扩展的测试数据生成器,其中一些具有已知的最佳解决方案。启发式为OP的“ABC”示例(我自己的8项node_set
)和已知最佳解决方案的可伸缩测试数据找到最佳解决方案如果性能不够好,至少这是第一次尝试,并且开始了一个测试“研讨会”,以改进启发式
测试结果
简短、相对琐碎的案例似乎没有问题
这个
"Quality <1000, cycling subsequence" (sequence length 501)
很有趣。通过使用{0, 1}
关系集对节点进行分组,质量分数几乎可以增加一倍。启发式算法找到这个最优序列。质量1000是不太可能的,因为这些双链接组需要每隔一段时间通过单个链接节点相互连接(例如{'AA': {0, 1}, 'AB': {0, 1}, ..., 'AZ': {0, 1}, <...single link here...> 'BA': {1, 2}, 'BB': {1, 2}, ...}
)对于每个节点关系很少的测试数据,性能仍然很好
旅行推销员问题(TSP)的困难之一是知道何时有一个最优解。即使从接近最优或最优的开始,启发式算法似乎也不会更快地收敛
当关系数量非常少时,即使有许多节点,性能也相当好,结果可能接近最优
这更像是OP生成的数据,也更像是经典的旅行商问题(TSP),其中每个城市对(对于“城市”读取“节点”)之间有一组距离,并且节点通常彼此连接紧密。在这种情况下,节点之间的链接是部分的,因此无法保证任何2个节点之间的链接
在这种情况下,启发式算法的时间性能要差得多。对于
n
节点,每个节点的随机关系介于0和n
之间。这可能意味着更多的互换组合产生更好的质量,互换和质量检查本身成本更高,在启发式收敛到最佳结果之前需要更多的过程。在最坏的情况下,这可能意味着O(n^3)随着节点和关系数量的增加,性能会下降(请注意
n=200
3分钟和n=500
70分钟之间的差异)。因此,目前启发式算法可能不适用于数千个高度互连的节点此外,由于暴力解决方案在计算上是不可行的,因此无法准确地知道该测试结果的质量
6861 / 200 = 34.3
和41999 / 500 = 84.0
节点对之间的平均连接看起来并不太遥远启发式和蛮力求解器代码
代码术语表
node_set
:一组带有标签的节点,其形式为'unique_node_id': {relation1, relation2, ..., relationN}
。每个节点的关系集可以不包含任何关系,也可以包含任意数量的关系node
:由node_id
(键)和一组关系(值)组成的键值对relation
:OP使用的是一个数字。如果两个节点都共享关系1
,并且它们是序列中的邻居,则会将1
添加到序列的质量中sequence
:与quality
分数相关联的一组有序节点ID(例如,.[A]、[B]、[C'])。质量分数是序列中节点之间共享关系的总和。启发式的输出是具有最高质量分数的一个或多个序列candidate
:当前正在执行的序列调查看它是否是高质量的方法
通过对链接项中是否存在每个关系进行稳定排序来生成种子序列
初始呈现的顺序也是种子序列之一,以防接近最优
对于每个种子序列,交换成对的节点以寻找更高的质量分数
对每个种子序列执行一轮。轮是一种类似于外壳排序的序列传递,首先在一定距离上交换节点对,然后缩小距离,直到距离为1(交换近邻)。仅保留质量高于当前最高质量分数的序列
如果在这一轮中发现了一个新的最高质量分数,则剔除除最高分数之外的所有候选分数,并使用最高分数作为种子重复4次。否则退出
测试和测试数据生成器
该启发式算法已经通过使用小型
node_sets
、数百到10000个节点的缩放数据(关系非常简单)和一个随机的、高度互联的node_set
进行了测试,更像OP的测试数据生成器。完美的单链接序列、链接循环(内部链接和彼此链接的小子序列)和洗牌都有助于发现和修复弱点演出
启发式比暴力解决方案更“实用”,因为它省去了许多可能的组合。可能是元素交换产生的低质量序列实际上距离一次交换后的高质量分数只有一步之遥,但这种情况可能会在测试之前被剔除
该启发式算法似乎可以找到单链序列或首尾相连的循环序列的最优结果。它们有一个已知的最佳解决方案,启发式算法可以找到该解决方案,并且它们可能比实际数据更简单,问题更少
一个巨大的改进是引入了“增量”质量计算,它可以快速计算两元素交换产生的质量差异,而无需重新计算整个序列的质量分数
我在修改你的测试程序,并提出了这个解决方案,它给了我0次失败。虽然感觉像是一种启发,但它确实需要更多的测试和测试用例。函数假定键是唯一的,因此
['A', 'A', 'B', ...]
列表和arguments
字典中不存在所有元素:编辑:误读质量函数,这映射到可分离的旅行商问题
对于跨所有节点的
N
节点、P
属性和T
总属性,这应该能够在O(N + P + T)
或更好的情况下解决,具体取决于数据的拓扑结构让我们将问题转化为一个图,其中任意两个节点之间的“距离”为
-(number of shared properties)
。没有连接的节点将保持未链接状态。这将至少需要O(T)
来创建图形,也许需要另一个O(N + P)
来分段然后通过节点将“排序顺序”转换为“路径”。特别是,您需要最短的路径
此外,您还可以应用多种翻译来提高通用算法的性能和可用性:
|number of properties|
)李>https://en.wikipedia.org/wiki/Component_(graph_theory)#Algorithms
https://en.wikipedia.org/wiki/Shortest_path_problem#Undirected_graphs
相关问题 更多 >
编程相关推荐