最小可能的组最高值

时间:2016-10-16 14:49:08

标签: c++ arrays algorithm

我有一个未排序的随机正整数数组。 (例如{42,68,35,1,70,25,79,59,63,65})

我想将此数组划分为较小的组。原始数组中的2个相邻数字不能在同一组中。 (例如42& 68必须在不同的组中)

每组最大数量的总和应尽可能小。 (例如{42,68,35,1,70,25,79,59,63,65}的最佳组将是{79,70,68,63},{65,59,42,35,25} ,{1},总和为79 + 65 + 1 = 145。

我当前的算法将值与索引配对,按值的降序对数组进行排序,循环遍历数组,检查是否在组1中找到相邻索引,如果没有,则插入值,否则转到下一个小组等。

//std::vector<std::vector<int>> groups;
//std::vector<std::pair<int, int>> pairs; Sorted by first element (value)
//for each value in the array
for (int j = 0; j < n; j++)
    {
    bool notfound = false;
    //for each group
    for (int z = 0; z < groups.size(); z++)
    {
        //Check if the group doesn't have a value with index +/- 1 of current
        if ( std::find(groups[z].begin(),groups[z].end(), pairs[j].second + 1) == groups[z].end()
            && std::find(groups[z].begin(), groups[z].end(), pairs[j].second - 1) == groups[z].end())
        {
            //if not found in the group, add it
            groups[z].push_back(pairs[j].second);
            notfound = true;
            break;
        }
    }
    //If found, create new group and add there
    if (!notfound)
    {
        //First value of group is always it's biggest since
        //we use array sorted by value, so the first number can be
        //added to the sum
        totalSum += pairs[j].first;
        groups.push_back(std::vector<int>{pairs[j].second});
    }
}

我知道它有点乱。但代码的问题是,它每次都没有给出最佳总和。 (例如,使用上面的示例数组,此代码将为您提供组{79,70,68,65},{63,42,35,25},{59,1},总和为79 + 63 + 59 = 201 &gt; 145)

编辑:
该算法并不像我需要的那样工作。我正在寻找替代方法来解决这个或修改当前算法,该算法考虑了我的问题的部分问题。

阵列中可能有多个相同数字的实例,所以我不认为std::set会起作用。

2 个答案:

答案 0 :(得分:0)

我不会处理代码,但我会告诉你我的想法/算法,因为我更像是一个数学家而不是程序员。

在对所有数字进行分组后,原始数组的最大数量无疑也将成为该组中最大的数字。 (根据你的解释,我相信你也注意到了它。)

包含符号的原始数组示例:

a1,a2,a3,a4,a5,a6,a7,a8,a9,a10

想象一下a7是最大的数字。我们将关注其他数字“应该”与a7在同一组中。这些数字应该尽可能大,以便其他组具有较小的数字,这将减少所得的总和。考虑a7左侧的元素: 确定这些数字中哪一个是最大的:

1)a5 + a3 + a1

2)a5 + a2

3)a4 + a2

4)a4 + a1

编辑:我忽略了5)a3 + a1因为1)将大于5)(如果没有元素是负数)。但是,即使存在负面因素,下面的子功能也会根据需要处理它,因为它会考虑所有选项的总和。

请注意,每行中的这些数字都是非连续的。想出一个子函数,它将记下这些选项(在这种情况下为1),2),3)和4))给定一个数组(在这种情况下为[a1,a2,a3,a4,a5])并计算他们的总和,然后决定哪个总和是最大的,哪个选项是最大的金额。

找到该选项后,将该选项的编号与a7合并,并将它们放在第一组中。对a7的右侧做同样的事情:

1)a9

2)a10

并将1)(a9)或2)(a10)也放入该组。从原始数组中删除所有使用过的元素,并像递归函数一样重复此过程。

更通用的例子:

originalArray = [a1,a2,...,ax,a(x + 1),...,an]

假设最大元素是ax。

调用前面提到的子功能:

subFunction([a1,a2,...,a(x-2)]为数组); //对于左侧

它应该返回一个数组,称之为array1。

subFunction([a(x + 2),a(x + 3),...,an] as array); //对于右侧

它应该返回一个数组,称之为array2。

define:group1 = Union({ax},elementsOf(array1),elementsOf(array2))

修改:originalArray.removeElements(elementsOf(group1))

重复新originalArray的过程,这次将输出放入group2。然后是group3,然后是group4 ......直到originalArray为空......

如果您需要/需要帮助/示例,我可以编写可视化的基本代码。

答案 1 :(得分:0)

三组足够

让我们调用分配给X组最高(X)的最高元素。

首先,请注意,只要您的解决方案包含两个组X和Y,其中最高(X)<=最高(Y),如果您可以将X中的任何数字移动到Y而不违反没有相邻数字的约束属于Y,你会得到一个不会更糟糕的解决方案。

分配给元素i的组被约束为与分配给元素i-1的组不同,并且与分配给元素1 + i的组不同。也就是说,最多有2个组不允许(&#34;最多为&#34;因为元素i-1和i + 1可能在彼此相同的组中)。但它可以是任何其他组。假设我们有一个包含4个或更多组的解S,其中A,B和C分别是具有最高,第二高和第三高元素的组。 (实际上它们的相对顺序并不重要 - 重要的是它们中每一个中的最高元素至少与任何其他组中一样高。)然后对于任何3个不同的组X,Y和Z 不是 A,B或C中的任何一个,在S中3个连续组分配的任何块中,我们可以按如下方式更改中间元素的赋值,不用使得分更差:< / p>

AXA => ABA or ACA
AXB => ACB
AXC => ABC
AXY => ABY or ACY
BXA => BCA
BXB => BAB or BCB
BXC => BAC
BXY => BAY or BCY
CXA => CBA
CXB => CAB
CXC => CAC or CAB
CXY => CBY or CAY
YXZ => YAZ or YBZ or YCZ

(当X出现在字符串的开头或结尾时,实际上还有一些案例需要处理,但它们并不令人兴奋,所以我还没有将它们写出来。)请注意,每次替换,唯一改变的是分配给X的单个元素现在被分配给A,B或C中的一个。

因此,如果任何解决方案S使用4个或更多组,我们可以简单地遍历分配给某个非{A,B,C}组的每个元素,并使用上述转换之一将其分配给A,B或者C代替。在对每个这样的元素重复这个之后,我们现在拥有的是解决方案S&#39;该

  1. 仅使用3组A,B和C以及
  2. 并不比原来的解决方案更差,S。
  3. 由于我们可以为任意解决方案S执行这些步骤,这意味着我们需要考虑的唯一解决方案是最多使用3组的解决方案! :)

    (请注意,我们的算法赢得实际上以这种方式转换实际的具体解决方案 - 上面只是一个证明我们不需要打扰评估的草图首先是4种或更多颜色的溶液,因为它们不如最好的2组或3组溶液好。)

    算法

    现在我们已经限制了群组的数量,问题可以通过dynamic programming来解决,尽管如何有效地细分它并不是完全明显的。

    令v [1]为输入中的第一个元素,v [2]为第二个元素,依此类推。另外,让v [0] = 0.(我们将使用此特殊索引来描述使用少于3个组的部分解决方案。)

    设f(i,j,k,P)是仅由前i个元素组成的子问题可获得的最小和,在分配给A的最高元素位于j的附加约束下分配给B的最高元素位于位置k,最右边的位置(即元素i)分配给组P(可以是A,B或C)。首先注意这些参数足以确定所有3组中最高元素:

    highest(A) = v[j]
    highest(B) = v[k]
    highest(C) = f(i, j, k, P) - v[j] - v[k]
    

    (从问题状态的描述中省略分配给C的最高元素让我们得到一个O(n ^ 3)解决方案,而不是我们原本需要的O(n ^ 4)解决方案,尽管在使计算f()的情况有点不对称和奇怪的成本。)在任何情况下,我们可以如下计算f()(对于i,j,k和P的任何值,选择第一个匹配的行):

    f(0, 0, 0, _) = 0
    f(0, _, _, _) = infinity (can't assign anything to A or B when i=0)
    f(i, i, i, _) = infinity (can't assign elem i to A and B)
    
    f(i, i, k, A) = minimum of min(f(i-1, j, k, B), f(i-1, j, k, C)) - v[j] + v[i] over all 0 <= j < i and such that v[j] <= v[i]
    f(i, j, i, A) = infinity (can't assign elem i to A if it's the highest in B)
    f(i, j, k, A) = IF v[i] > v[j] THEN infinity ELSE min(f(i-1, j, k, B), f(i-1, j, k, C))
    
    f(i, i, k, B) = infinity (can't assign elem i to B if it's the highest in A)
    f(i, j, i, B) = minimum of min(f(i-1, j, k, A), f(i-1, j, k, C)) - v[k] + v[i] over all 0 <= k < i and such that v[k] <= v[i]
    f(i, j, k, B) = IF v[i] > v[k] THEN infinity ELSE min(f(i-1, j, k, A), f(i-1, j, k, C))
    
    f(i, i, k, C) = infinity (can't assign elem i to C if it's the highest in A)
    f(i, j, i, C) = infinity (can't assign elem i to C if it's the highest in B)
    f(i, j, k, C) = IF v[i] < min(f(i-1, j, k, A), f(i-1, j, k, B)) - v[j] - v[k] THEN infinity ELSE v[i] + v[j] + v[k]
    

    原始问题的最高组元素的最小可能和是f(n,j,k,P)在{A,B中的所有1&lt; = j,k&lt; = n和P中的最小值C}。可以使用标准DP回溯技术恢复组中的实际分区(基本上,先前的数组p [i] [j] [k] [P]记录最小化任何最小值的j或k的值。 。所有...&#34;表达式;这允许恢复前面的状态。)

    上述重复可以直接作为递归函数实现,但这将导致指数求解时间。要获得多项式时间解决方案,我们可以简单地应用memoisation - 也就是说,我们可以记录已经解决的子问题的解决方案值,而不是每次重新计算它们。

    1&lt; = i,j,k&lt; = n且P只能是3个不同组中的1个,因此有O(n ^ 3)个可能的子问题需要解决。如果i,j,k都是不同的,则计算f()需要O(1)时间;虽然当i = j或i = k时我们需要O(n)时间来找到O(n)项的最小值,但是只有O(n ^ 2)个这样的情况。因此总时间复杂度为O(n ^ 3)。这也是一个天真实现的内存要求,虽然可以通过利用计算f(i,...)的事实将其降低到O(n ^ 2),我们只需要访问f(i) -1,...),所以我们只需要保留i的前一个值的f()值,而不是之前的所有值。

    C ++实施

    充实基础案例并修复了错误后,我现在将上述重复实现为C ++程序。它正确地输出145作为示例,虽然我实际上没有实现记忆,但它只需要几毫秒。它还为我尝试的其他一些小序列提供了正确的输出。