【问题标题】:Calculating the Number of Possible Permutations that Meet a Requirement计算满足要求的可能排列的数量
【发布时间】:2018-10-04 20:37:29
【问题描述】:

这个问题已经困扰我好几天了,我不知道如何解决它。我已经非常努力地自己解决它,但现在我非常感谢一些帮助和正确方向的指针。

问题:

给定一组数字,以及每个数字可以大于或小于以下数字的最大限制,根据限制确定数字的有效排序数。

示例:

数字:20、30、36、40

一个数字可以大于以下数字的最大数量:16

一个数字可以小于以下数字的最大数量:8

这里将有 3 个有效的订单:

36 40 30 20

40 36 30 20

40 30 36 20

我设计了一种使用递归和树生成所有有效排列的方法,但不幸的是,在列表中有许多有效顺序的情况下(我相信接近 n!运行时间),这需要很长时间。我觉得好像有一种更快、更数学的方法可以使用我没有看到的组合学来解决这个问题。任何建议将不胜感激,谢谢!

编辑: 这是我想出的置换算法的代码。代码的最后一部分使用我上面给出的示例对其进行了测试。它是用 Python 3.6 编写的。

class block:
    def __init__(self, val, children):
        self.val = val
        self.children = children


# Gets all the possible children of the current head within the limits
def get_children(head, nums, b, visited, u, d):
    global total
    if all(visited):
        total += 1
        return

    for i in range(b):
        if not visited[i]:
            if head.val - nums[i] <= d and nums[i] - head.val <= u:
                head.children.append(block(nums[i], []))
                visited[i] = True
                get_children(head.children[-1], nums, b, visited, u, d)
                visited[i] = False


# Display all the valid permutations of the current head
def show(head, vals, b):
    vals.append(head.val)
    if head.children == [] and len(vals) == b:
        print(*vals)
        return
    for child in head.children:
        show(child, vals[:], b)


# Test it out with the sample
b, nums, u, d = 4, [20, 30, 36, 40], 8, 16
visited = [False for x in range(b)]
total = 0
heads = []
for i in range(b):
    heads.append(block(nums[i], []))
    visited[i] = True
    get_children(heads[-1], nums, b, visited, u, d)
    visited[i] = False
    show(heads[-1], [], b)
print(total)

打印出来:

36 40 30 20
40 30 36 20
40 36 30 20
3

【问题讨论】:

  • 我认为这个问题实际上可能很难——这看起来很相似:math.stackexchange.com/questions/2251021/… 你为什么不分享你最快的递归方法,也许这会帮助人们看到优化选项?
  • 这不类似于计算图中的所有哈密顿路径吗?
  • @גלעדברקן 是的。但这会让事情变得更容易吗?
  • 你的目标是一定的时间复杂度,还是设定大小?
  • @m69 我不知道,但它会将其识别为可能已知解决方案的已知问题。

标签: algorithm math permutation combinatorics


【解决方案1】:

用 10 个相等的数字尝试您的方法,运行时间为 35 秒。

我注意到的第一件事是该函数只需要列表头中的最后一个条目,因此该函数可以简化为采用整数而不是列表。下面的代码做了三个简化:

  1. 为 head 传入一个整数而不是一个列表
  2. 将总计更改为返回值而不是全局值
  3. 避免存储孩子(因为只需要订购数量)

简化后的代码如下:

def get_children(head, nums, b, visited, u, d):
    if all(visited):
        return 1
    t = 0
    for i in range(b):
        if not visited[i]:
            if head - nums[i] <= d and nums[i] - head <= u:
                head2 = nums[i]
                visited[i] = True
                t += get_children(head2, nums, b, visited, u, d)
                visited[i] = False
    return t

# Test it out with the sample
nums, u, d = [20, 30, 36, 40], 8, 16
b = len(nums)
visited = [False for x in range(b)]
total = 0
for i in range(b):
    head = nums[i]
    visited[i] = True
    total += get_children(head, nums, b, visited, u, d)
    visited[i] = False
print(total)

10 个相等数字的列表需要 7 秒。

我注意到的第二件事是(对于特定的测试用例)get_children 的返回值仅取决于访问中为 True 的内容和 head 的值。

因此我们可以缓存结果以避免重新计算它们:

cache={}
# Gets all the possible children of the current head within the limits
def get_children(head, nums, b, visited, u, d):
    if all(visited):
        return 1
    key = head,sum(1<<i for i,v in enumerate(visited) if v)
    result = cache.get(key,None)
    if result is not None:
        return result
    t = 0
    for i in range(b):
        if not visited[i]:
            if head - nums[i] <= d and nums[i] - head <= u:
                head2 = nums[i]
                visited[i] = True
                t += get_children(head2, nums, b, visited, u, d)
                visited[i] = False
    cache[key] = t
    return t

这个版本只需要 0.03 秒就可以列出 10 个相等数量的列表(即比原来快 1000 倍。)

如果您使用不同的 b/u/d 值执行多个测试用例,您应该在每个测试用例开始时重置缓存(即 cache={})。

【讨论】:

  • 就是这样!我有一半怀疑答案可能涉及存储值以避免重蹈覆辙,但如何做到这一点超出了我的范围。我也非常喜欢您如何更改函数以返回总数。我只有最后一个问题,为什么我们在初始化当前键的值时会移位?
  • 位移将 True 和 False 数组转换为二进制数,其中 1 表示 True,0 表示 False。您可以改为通过“key=head,tuple(visited)”转换为元组,这实际上比我的方法更快,但会为缓存使用更多内存。
  • 好的,我现在明白了。谢谢!
【解决方案2】:

正如在 cmets 中所指出的,在此处找到所有有效排列等同于识别有向图中的所有哈密顿路径,这些路径将您的数字作为顶点和边,对应于允许彼此跟随的每一对数字。

这是一个非常简单的 Java (IDEOne) 程序来查找此类路径。这是否使您的问题易于处理取决于图表的大小和分支因子。

public static void main(String[] args)
{
  int[] values = {20, 30, 36, 40};

  Vertex[] g = new Vertex[values.length];
  for(int i=0; i<g.length; i++) 
    g[i] = new Vertex(values[i]);

  for(int i=0; i<g.length; i++) 
    for(int j=0; j<g.length; j++)
      if(i != j && g[j].id >= g[i].id-16 && g[j].id <= g[i].id+8)
        g[i].adj.add(g[j]);

  Set<Vertex> toVisit = new HashSet<>(Arrays.asList(g));
  LinkedList<Vertex> path = new LinkedList<>();
  for(int i=0; i<g.length; i++)
  {
    path.addLast(g[i]);
    toVisit.remove(g[i]);
    findPaths(g[i], path, toVisit);
    toVisit.add(g[i]);
    path.removeLast();
  }
}

static void findPaths(Vertex v, LinkedList<Vertex> path, Set<Vertex> toVisit)
{
  if(toVisit.isEmpty())
  {
    System.out.println(path);
    return;
  }

  for(Vertex av : v.adj)
  {
    if(toVisit.contains(av))
    {
      toVisit.remove(av);
      path.addLast(av);
      findPaths(av, path, toVisit);
      path.removeLast();
      toVisit.add(av);
    }
  }
}

static class Vertex
{
  int id;
  List<Vertex> adj;

  Vertex(int id)
  {
    this.id = id;
    adj = new ArrayList<>();
  }

  public String toString()
  {
    return String.valueOf(id);
  }
}

输出:

[36, 40, 30, 20]
[40, 30, 36, 20]
[40, 36, 30, 20]

【讨论】:

  • 我将您的答案转换为 Python,它完美运行!不幸的是,运行时间似乎与我之前的尝试差不多。我认为解决方案可能不涉及生成所有排列,因为它的运行时间可能很差。
猜你喜欢
  • 1970-01-01
  • 2019-07-13
  • 1970-01-01
  • 2020-01-08
  • 1970-01-01
  • 2017-09-04
  • 1970-01-01
  • 2013-03-22
  • 1970-01-01
相关资源
最近更新 更多