如何解决贪婪算法将数字列表列表分成两个分区,Python 中每个数字的数量相同
我有一个随机正整数子列表的列表。此列表由 3 个参数控制:
-
max_num
:每个子列表中允许的最大整数,例如如果max_num = 3
,列表看起来像[[1,3],[3],[1,2,[1],...]
; -
max_length
:每个子列表的最大整数个数; -
n_gen
:生成的子列表的个数,即列表的长度。
您可以使用以下代码生成这样的列表
import random
random.seed(10)
def random_numbers(length,max_num):
return [random.randint(1,max_num) for _ in range(length)]
max_num = 3
max_length = 3 # I want max_length=10
n_gen = 10 # I want n_gen=200
lst = [random_numbers(random.randint(1,max_length),max_num) for _ in range(n_gen)]
现在我想将列表分成两个分区,每个分区的每个数字的数量都相同。例如,如果 lst = [[1,[2,[3]]
,其中一种解决方案是 bipartition = [[[1,[3]],[[2,3]]]
。
我设法为所有可能的双分区编写了以下暴力枚举,这适用于小参数。
from itertools import product
lst1 = []
lst2 = []
for pattern in product([True,False],repeat=len(lst)):
lst1.append([x[1] for x in zip(pattern,lst) if x[0]])
lst2.append([x[1] for x in zip(pattern,lst) if not x[0]])
bipartitions = []
for l1,l2 in zip(lst1,lst2):
flat1 = [i for l in l1 for i in l]
flat2 = [i for l in l2 for i in l]
if sorted(flat1) == sorted(flat2):
bipartitions.append([l1,l2])
for bipartition in bipartitions:
print(bipartition)
输出:
[[[1,2],1,[3,2]],[[1],1],2]]]
[[[1,[[1,[3]]]
[[[1,[3]]]
[[[2,2]]]
[[[1],2]]]
然而,当参数变大时,这变得不可行。现在我想生成每个数字具有相同数量的随机二分,我猜贪心算法会做。对于我当前的任务,我需要使用
max_num = 3
max_length = 10
n_gen = 200
有什么建议吗?
编辑:我知道在某些情况下根本不可能进行这种二分。我的想法是,当贪婪算法在最大数量的建议(例如,如果足够快,则为 1000)后建议的二分时,我们应该相信不存在这样的二分。当参数很大时,即使检查是否存在这种二分也是不可行的。
解决方法
天哪,这真是太棒了。首先,让我陈述显而易见的事情。贪心算法是确定性的,因为它总是会选择最佳路径。其次,实际上能够将某些东西分成两部分的可能性非常非常小。我还建议,如果您想生成二分区,尝试从这样的随机集合中找到它们不是一个好主意。
无论如何,进入代码。首先,让我说代码并不漂亮,也没有完全优化。到最后,我什至在某些领域都不是 Pythonic,但它们都很容易修复。我已经在这工作了几个小时,但这是一个有趣的项目。名单的复制是主要嫌疑人。您可以根据自己的时间重新编写并优化它。我也不能保证它没有错误,但我很确定它是。唯一的例外是,如果您想要任何结果,您需要确保它至少进行一次“仔细”搜索。这让我想到了下一点,算法本身。
我们从一个非常标准的贪心算法开始。我们从我们的分区对象中选择一个索引,WLOG,将它分配给左二分区。接下来我们看看插入所有剩余列表的所有可能方法。我们选择最接近 0 的那个。我们重复直到遇到某个断点,然后我们切换到您的穷举算法。
现在,我们很可能没有为您的常量的高值找到分区。我相信这只是一个统计问题,而不是算法的问题,但我可能是错的。
我还进行了一个粗略的可行性测试,您很快就会发现,所有随机生成的嵌套列表中约有 90% 可以立即丢弃,因为无法进行二分。
然而,贪婪算法的添加现在允许我在我的机器上从测试约 15 个长度的分区到约 30 个长度的分区,并且很成功地找到了一个。它还可以在不到 10 秒的时间内运行,例如3,3,40,12 作为其常数。
最后,这里是代码 请注意,我只让它生成一个分区进行测试,因此您可能需要运行几次才能获得可行的分区:
from itertools import product
import random
import datetime
import time
import sys
MAX_NUM = 3
MAX_LEN = 3
NUM_GEN = 200
NSWITCH = 12
random.seed(time.time())
def feasible(partitionee):
'''Does a rough test to see if it is feasible to find a partition'''
counts = [0 for _ in range(MAX_NUM)]
flat = [x for sublist in partitionee for x in sublist]
for n in flat:
counts[n-1] += 1
for n in counts:
if n % 2 != 0:
return False
return True
def random_numbers(length,max_num,n_lists):
'''Create a random list of lists of numbers.'''
lst = []
for i in range(n_lists):
sublist_length = random.randint(1,length)
lst.append([random.randint(1,max_num) for _ in range(sublist_length)])
return lst
def diff(lcounts,rcounts):
'''Calculate the difference between the counts in our dictionaries.'''
difference = 0
for i in range(MAX_NUM):
difference += abs(lcounts[i] - rcounts[i])
return difference
def assign(partition,d,sublist):
'''Assign a sublist to a partition,and update its dictionary.'''
partition.append(sublist)
for n in sublist:
d[n-1] += 1
def assign_value(d1,d2,sublist):
'''Calculates the loss of assigning sublist.'''
for n in sublist:
d1[n-1] += 1
left_score = diff(d1,d2)
for n in sublist:
d1[n-1] -= 1
d2[n-1] += 1
right_score = diff(d1,d2)
for n in sublist:
d2[n-1] -= 1
return (left_score,right_score)
def greedy_partition(left,right,lcounts,rcounts,i,partitionee):
# Assign the i:th sublist to the left partition.
assign(left,partitionee[i])
del partitionee[i]
for _ in range(NUM_GEN - NSWITCH):
# Go through all unassigned sublists and get their loss.
value_for_index = {}
for i,sublist in enumerate(partitionee):
value = assign_value(lcounts,sublist)
value_for_index[i] = value
# Find which choice would be closest to 0 difference.
min_value = 100000000000 # BIG NUMBER
best_index = -1
choose_left = True
for key,value in value_for_index.items():
if min(value) < min_value:
min_value = min(value)
choose_left = value[0] < value[1]
best_index = key
# Assign it to the proper list.
if choose_left:
assign(left,partitionee[best_index])
else:
assign(right,partitionee[best_index])
del partitionee[best_index]
return diff(lcounts,rcounts)
# Create our list to partition.
partition_me = random_numbers(MAX_LEN,MAX_NUM,NUM_GEN)
start_time = datetime.datetime.now()
# Start by seeing if it's even feasible to partition.
if not feasible(partition_me):
print('No bipartition possible!')
sys.exit()
# Go through all possible starting arrangements.
min_score_seen = 100000000000 # BIG NUMBER
best_bipartition = []
for i in range(NUM_GEN):
# Create left and right partitions,as well as maps to count how many of each
# number each partition has accumulated.
left = []
right = []
lcounts = [0 for i in range(MAX_NUM)]
rcounts = [0 for i in range(MAX_NUM)]
# Copy partitionee since it will be consumed.
partition = partition_me.copy()
# Do greedy partition.
score = greedy_partition(left,partition)
if score < min_score_seen:
min_score_seen = score
best_bipartition = [left] + [right]
# Now that we've been greedy and fast,we will be careful and slow.
# Consider all possible remaining arrangements.
print('Done with greedy search,starting careful search.')
left = best_bipartition[0]
right = best_bipartition[1]
for pattern in product([True,False],repeat=len(partition)):
lst1 = left + ([x[1] for x in zip(pattern,partition) if x[0]])
lst2 = right +([x[1] for x in zip(pattern,partition) if not x[0]])
left_flat = [x for sublist in lst1 for x in sublist]
right_flat = [x for sublist in lst2 for x in sublist]
if sorted(left_flat) == sorted(right_flat):
print('Found bipartition by careful search:')
print([lst1] + [lst2])
break
end_time = datetime.datetime.now()
print('Time taken: ',end='')
print(end_time - start_time)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。