将组分成几乎相等的堆栈

时间:2009-03-11 13:12:08

标签: algorithm language-agnostic sorting

我有一份文件清单,我希望在网页上显示按名称的第一个字母分组的三个列。

简而言之,就像这样:

A | C | E
A | D | F
B | D | F
B | D | F
  | D | 

与Windows资源管理器视图样式的一个重要区别在于我希望字母彼此保持一致。没有打破中间组。为了适应这种情况,我不在乎一列是否有一些条目太高。

我首先按名称对文档数组进行排序,然后将它们拆分为嵌套数组。所以我知道(或者很容易找到):

  • 有多少独特字母
  • 每组中有多少个字母
  • 总共有多少条目
  • 每列中应该有多少值的平均值(理想情况但不是必须的)

我不关心你的答案是什么。我正在寻找算法而不是实现,所以你可以编写你喜欢的任何东西(除了Fortran)。 HTML中的解释也可能很难。

我邀请有人在标签上疯狂,因为我想不出任何相关的和不,这不是作业,所以请不要这样做。

7 个答案:

答案 0 :(得分:5)

如果你看一下这样的问题,也许会有所帮助:

对于您的示例,您有一个这样的字符串:

AA BB C DDDD E FFF

空间位置是您可以开始新列的位置。在其他地方,您不得在同一列中保留相同的字母。 所以你实际上可以像这样标记空间位置:

AA1BB2C3DDDD4E5FFF

现在你有5个位置,你可以打破或不打破,因为这是一个二元决定,使用0和1的字符串为此和暴力强制每个可能的组合:

12345

00000 -> no break at all, column count = 1, max. lines = 13
...
01010 -> your example, column count = 3, max. lines = 5
...
11111 -> breaks everywhere, column count = 6, max. lines = 4

这是一次蛮力尝试,但您可以很容易地看到1的计数会影响列数(列数= 1的数量+ 1),并且您希望最小化最大值。这些线应该可以以某种方式计算,而不必测试每个组合。

EDIT2:没有认识到你想要3列,这会让你更容易,因为你知道你只有3个1,但它仍然是暴力。

编辑:我赞成的另一种方法:

写信如下:

A B C D E F
2 2 1 4 1 3

您现在可以加入彼此相邻的字母。始终选择计数最小的两个字母:

2 2 1 4 1 3 - lowest = "2 1"
2  3  4 1 3 - lowest = "1 3"
2  3  4  4  - lowest = "2 3"
  5   4  4  - stop now, as we have 3 columns now

Result: AABBC, DDDD, EFFF

这可能不会导致最佳解决方案,但我认为这是一种解决问题的好方法。

答案 1 :(得分:3)

嗯,您可以期望每列中都有一些额外的行。我的意思是,如果你有2个A,2个B和33个C,那么第三个列与其他列相比会很高。

这不是背包问题,因为它们必须按顺序排列。

你能做的是:

  • 计算物品数量。
  • 查看前三分之一会落在哪里。
  • 如果它正是一个字母更改点,那么你很幸运:))
  • 如果没有,那么最小化第三部分分割点和上一个/下一个字母变化点之间的距离 - 即如果有一个字母改变之前的2个条目和之后的10个条目,则转到前一个。
  • 最后,取其余部分,除以2,然后按照相同的逻辑从平均值中尽可能地分开。

答案 2 :(得分:3)

除非输入也是有限的,否则在给定约束条件的情况下,没有针对此问题的通用解决方案。例如,考虑一个集合,其中单个文档以字母A,B,C,E和F开头,15个(或百万个)文档以D开头。为了将所有D组合在一列中,列长度必须至少为15.如果使用两列以上,则最好第一列的长度为3,第二列的长度为15(或一百万),第三列的长度为2.这违反了你的“在几个条目中”约束。

您需要确定列上没有中断字母的约束是否足够重要,以保证列大小之间可能存在较大的差异,或者输入是否受到限制,以便可以使用给定的约束来解决问题。就个人而言,我会重新考虑界面解决一个优化问题只是为了保持字母在一起似乎有点矫枉过正。

答案 3 :(得分:0)

我认为你应该从定义一种“措施”开始,它会告诉你哪种布局是最好的,例如取所有列的(average_size - actual_size(列))^ 2的总和。然后,因为你总是有3列(是吗?),所以可以合理地快速地采取所有可能的划分并找到最大化你的度量。

答案 4 :(得分:0)

首先,对文档进行传递以构建一个letter-> count元组数组。

第一个条目是(数组中的第一个字母) - >文件0

然后通过遍历数组找到应该出现在第二列和第三列中的条目,添加计数,但在通过第2和第3列的阈值之前停止(这是1/3和2 / 3总数。)

答案 5 :(得分:0)

这个问题适用于递归解决方案---可能是经典的动态编程,尽管我还没有完全解决。

您有一定数量的潜在分割点和一定数量的分割。你应该能够拥有像

这样的东西
(splits, max_ht, min_ht) = split_list(list, requested_splits, 
                                      curr_max, curr_min)

该函数应迭代可能的分裂点,并在列表的其余部分递归调用自身(少一个请求的分割)。如,

def split_list(list, requested_splits, curr_max, curr_min):
    best_splits = []
    best_split_len = curr_max-curr_min
    best_max = curr_max
    best_min = curr_min

    if requested_splits == 0:
        return (best_splits, curr_max, curr_min)
    for candidate in candidate_split_points:
        this_max = max(curr_max, len(<list up to split point>)
        this_min = min(curr_min, len(<list up to split point>)
        (splits, new_max, new_min) = split_list(<list after split point>,
                                                requested_splits-1,
                                                this_max, this_min)
        if (new_max-new_min) < best_split_len:
            best_split_len = new_max - new_min
            best_max = new_max
            best_min = new_min
            best_splits = [candidate] + splits
    return (best_splits, best_max, best_min)

答案 6 :(得分:0)

这是你可以尝试的东西。由于您知道理想的列号(n),因此请将第一列中的所有元素推送到第一列。

按照您认为合适的时间重复下一步...它是一个迭代算法,因此结果在前几次迭代中变得更快,然后您的返回开始减少。

按顺序运行列。

让当前列中的项目数为numCurrent。

如果numCurrent&lt; n,跳过这一栏。

跟踪以第一个字母(groupFirst)开头的元素和当前列的最后一个字母(groupLast)。

计算前一列(如果有的话)的项目数为numPrev。如果abs(n-numCurrent)> abs(n-numPrev + groupFirst),将groupFirst移动到上一列。

重新计算numCurrent。

和以前一样,如果有下一列,如果abs(n-numCurrent)&gt;,则将groupLast移入其中。 ABS(正numNext + groupLast)。

冲洗并重复。漱口水越多,它应该看起来更整洁。将会有一个点不再有可能发生变化,也可以指出它可以继续发展。您决定迭代次数。