【问题标题】:Algorithm for finding optimal multiple partitionings based on some costs基于一些代价寻找最优多重分区的算法
【发布时间】:2021-11-23 02:52:42
【问题描述】:

我有一种情况,我需要根据一些成本在数组中找到最佳拆分位置。问题是这样的:

作为输入,我有一个按整数时间戳排序的事件数组,作为输出,我想要一个索引数组,它将输入数组分成许多部分。输出数组需要是最优的(下面会详细介绍)。

struct e {
    int Time;
    // other values
}

Example Input:  [e0, e1, e2, e3, e4, e5, ..., e10]
Example output: [0, 2, 6, 8] (the 0 at the start is always there)

使用上述示例,我可以使用拆分索引将原始数组划分为 5 个子数组,如下所示:

[ [], [e0, e1], [e2, e3, e4, e5], [e6, e7], [e8, e9, e10] ]

这个示例解决方案的成本是子数组之间“距离”的总成本:

double distance(e[] arr1, e[] arr2) {
    // return distance from arr1 to arr2, order matters so non-euclidean
}

total cost = distance([], [e0, e1]) + distance([e0, e1], [e2, e3, e4, e5]) + ...

此时有助于理解实际问题。

输入数组表示某个时间的音符(即 MIDI 文件),我想将 MIDI 文件拆分为最佳吉他指法。因此,每个音符子阵列代表一个和弦(或在单个指法中组合在一起的旋律)。两个子阵列之间的距离表示从一种指法模式移动到另一种指法模式的难度。目标是找到用吉他弹奏歌曲的最简单(最佳)方式。

我还没有证明它,但对我来说,这看起来像是一个 NP-Complete 或 NP-Hard 问题。因此,如果我可以将其简化为另一个已知问题并使用已知的分治算法,这可能会有所帮助。此外,可以使用更传统的搜索算法(A* ?)来解决这个问题。它可能是有效的,因为我们可以比在常规图中更快地过滤掉错误的解决方案(因为从技术上讲,输入是一个完整的图,因为每个指法都可以从任何其他指法到达)。

我无法决定最好的方法是什么,所以我目前陷入困境。任何提示或想法将不胜感激。

【问题讨论】:

  • 有没有规定第一个子数组总是[],或者某些子数组是不允许的,或者你需要最小子数组数?更具体地说,是什么阻止您获取整个阵列?
  • @kcsquared 是的,第一个子数组总是空的,因为在播放一首歌曲时,您从没有手指在任何琴弦上开始。当且仅当歌曲包含最多 6 个不同的音符并且它们可以同时播放时,从技术上讲,整个数组可以被取出。不过,这可能永远不会发生在任何真正的歌曲中。

标签: algorithm search midi dijkstra


【解决方案1】:

这可能不是 NP 难的。

形成一个图,其节点与(连续的)子数组一一对应。对于每对节点 u, v,其中 u 的右边界是 v 的左边界,添加一条从 u 到 v 的弧,其长度由distance() 确定。为左边界为起点的每个节点创建一个具有出向弧的人工源。创建一个人工接收器,其中每个节点的右边界为末端的传入弧。

现在我们可以通过有向无环图的线性时间(在图的大小中,因此在感兴趣的参数中为三次)算法找到从源到汇的最短路径。

【讨论】:

    【解决方案2】:

    这有点晚了,但我确实解决了这个问题。我最终为此使用了稍微修改过的 Dijkstra 版本,但任何寻路算法都可以工作。我也尝试了 A*,但由于问题的非欧几里德性质,找到一个好的启发式算法非常困难。

    Dijkstra 的主要变化是,在某些时候我已经可以看出一些未访问的节点无法提供最佳结果。这大大加快了算法的速度,这也是我没有选择 A* 的原因之一。

    算法本质上是这样工作的:

    search()
      visited = set()
      costs = map<node, double>()
      
      // add initial node to costs
    
      while costs is not empty:
        node = node with minimum cost in costs
    
        if current.Index == songEnd:
          // backtrack from current to get fingering for the song
          return solution
    
        visited.add(node)
    
        foreach neighbour of node:
          if visited node:
            continue
          
          newCost = costs[node] + distance(node, neighbour)
          add neighbour with newCost to costs
    
        
      // we can remove nodes that have a higher cost but
      // which have a lower index than our current node
      // this is because every fingering position is reachable
      // from any fingering positions
      // therefore a higher cost node which is not as far as our current node
      // cannot provide an optimal solution
      remove unoptimal nodes from costs
    
      remove node from costs
    
    // if costs ends up empty then it is impossible to play this song
    // on guitar (e.g. more than 6 notes played at the same time)
    

    这个算法的神奇之处在于获取邻居和计算两个节点之间的距离,但这些与这个问题无关。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-07
      • 2023-03-25
      • 2019-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-03
      相关资源
      最近更新 更多