【问题标题】:How to divide an array into 3 parts with the sum of each part roughly equal如何将一个数组分成 3 个部分,每个部分的总和大致相等
【发布时间】:2012-01-07 17:34:02
【问题描述】:

我有一个排列好的数组,我想把它分成 3 部分,使它们的总和彼此最接近。

例如:我有这个数组: 10, 8, 8, 7, 6, 6, 6, 5 所以它将分为3部分,如: p1 {10,8} 总和 = 18 p2 {8,7,6} 总和 = 21 p3 {6,6,5} 总和 = 17

【问题讨论】:

  • 我把一个数组分成两部分,它奏效了。但我还没有将它分成 3 的任何想法
  • 10+7=178+6+6=208+6+5=19 不是更合适吗?
  • 这样更好,但我想按顺序对它们进行分组,在这种情况下是从 i=0 到 i=7
  • 根据您的示例,我认为您想拆分数组,保持原始顺序。拆分部分的总和也必须几乎与上一个/下一个值的值相同。可能需要额外的信息: - 使用哪些小数 (0-10) of (0-100) of (0-endless)... - 使用了多少小数?
  • 对不起,我不明白你的想法。

标签: c# algorithm


【解决方案1】:

试试下面的代码

int total = 0, partSum = 0, partIndex = 0;
int noOfParts = 3; //Initialize the no. of parts
int[] input = { 10, 8, 8, 7, 6, 6, 6, 5 };
int[] result = new int[noOfParts]; //Initialize result array with no. of locations equal to no. of parts, to store partSums
foreach (int i in input) //Calculate the total of input array values
{
    total += i;
}
int threshold = (total / noOfParts) - (total / input.Length) / 2; //Calculate a minimum threshold value for partSum
for (int j = input.Length - 1; j > -1; j--)
{
    partSum += input[j]; //Add array values to partSum incrementally
    if (partSum >= threshold) //If partSum reaches the threshold value, add it to result[] and reset partSum  
    {
        result[partIndex] = partSum;
        partIndex += 1;
        partSum = 0;
        continue;
    }
}
if (partIndex < noOfParts) //If no. of parts in result[] is less than the no. of parts required, add the remaining partSum value
{
    result[partIndex] = partSum;
}
Array.Reverse(result);
foreach (int k in result)
{
    Console.WriteLine(k);
}
Console.Read();     

我已经用数组中的各种值(按降序排列)和不同的值进行了测试。部分(3,4,5...)并取得了良好的效果。

【讨论】:

  • 为什么我们有这个代码? int threshold = (total / parts)-(total / array.Length) / 2;
  • 只是计算一个最小阈值,使三个总和大于阈值。它是通过将数组的总数除以编号来计算的。部分(在这种情况下,56/3 = 18.66 四舍五入为 18)并减去平均值的一半((56/8)/2 = 7/2 = 3.5 四舍五入为 3)。所以这种情况下的完整方程是 (56/3)-(56/8)/2 = 15。
【解决方案2】:
// calculate total
total = 0;
for(i = 0; i != size; ++i) {
   total += array[i];
}

// partition
n_partitions = 3;
current_partition = 1;
subtotal = array[0];
for(i = 1; i != size; ++i) {
   if(subtotal + array[i] > total / n_partitions) {
      // start new partition;
      current_partition++;
      subtotal = array[i];
   } else {
      // push to current partition
      subtotal += array[i];
   }
}

【讨论】:

    【解决方案3】:

    你能试试我的样品吗,这可能对你有帮助

    我的算法: 1/通过输出数组的数量计算数组数字的平均值(你的帖子中的exp:value = 3)

    2/ 对数组数字求和,直到 Sum 与平均值相比有最小差距(以 1/ 计算)

    3/ 执行第 2 步,直到到达数组编号的末尾

    我使用 C# 3.5 进行测试

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Collections;
    
    namespace WindowsFormsApplication2
    {
        public partial class Form2 : Form
        {
            public Form2()
            {
                InitializeComponent();
            }
    
            ArrayList inputValue = new ArrayList();
            int avgValue = 0;
            bool isFinish = false;
            private void button1_Click(object sender, EventArgs e)
            {
                #region Init data
                isFinish = false;
                avgValue = 0;
                inputValue.Clear();
                listBox1.Items.Clear();
                //assum you input valid number without space and in desc sorting order 
                string[] arrNumber = textBox1.Text.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                int numberOfBreak = 3;
                int record = Convert.ToInt32(arrNumber[0]);//update the record with the maximum value of the array numbers
                for (int i = 0; i < arrNumber.Length; i++)
                {
                    inputValue.Add(Convert.ToInt32(arrNumber[i]));
                }
    
                foreach (object obj in inputValue)
                {
                    avgValue += (int)obj;
                }
                avgValue = avgValue / numberOfBreak;
                #endregion
                int lastIndex = 0;
                while (!isFinish)
                {
                    int index = GetIndex(lastIndex);
                    string sResult = "";
                    for (int i = lastIndex; i <= index; i++)
                    {
                        sResult += inputValue[i].ToString() + "-";
                    }
                    listBox1.Items.Add(sResult);
                    if (index + 1 < inputValue.Count)
                    {
                        lastIndex = index + 1;
                    }
                    sResult = "";
                }
            }
    
            private int GetIndex(int startIndex)
            {
                int index = -1;
                int gap1 = Math.Abs(avgValue - (int)inputValue[startIndex]);
                int tempSum = (int)inputValue[startIndex];
                if (startIndex < inputValue.Count - 1)
                {
    
                    int gap2 = 0;
                    while (gap1 > gap2 && !isFinish)
                    {
                        for (int i = startIndex + 1; i < inputValue.Count; i++)
                        {
                            tempSum += (int)inputValue[i];
    
                            gap2 = Math.Abs(avgValue - tempSum);
                            if (gap2 <= gap1)
                            {
                                gap1 = gap2;
                                gap2 = 0;
                                index = i;
                                if (startIndex <= inputValue.Count - 1)
                                {
                                    startIndex += 1;
                                }
                                else
                                {
                                    isFinish = true;
                                }
                                if (startIndex == inputValue.Count - 1)
                                {
                                    index = startIndex;
                                    isFinish = true;
                                }
                                break;
                            }
                            else
                            {
                                index = i - 1;
                                break;
                            }
                        }
                    }
    
    
                }
                else if (startIndex == inputValue.Count - 1)
                {
                    index = startIndex;
                    isFinish = true;
                }
                else
                {
                    isFinish = true;
                }
                return index;
            }
        }
    }
    

    【讨论】:

    • 我看到我的样品不好。太长了=))
    【解决方案4】:

    这就像two-Partition 问题,它是 NP-Hard 但不是在强烈意义上,你可以有一个 O(nK) 算法,其中 K 是你的输入总和的大小,请参阅 pseudo polynomial time algorithm for subset sum,另请参阅我的答案对于divide-list-in-two-parts-that-their-sum-closest-to-each-other,但在您的情况下,您应该只添加另一个维度来处理它。

    【讨论】:

      【解决方案5】:

      原始海报已经有一个可行的解决方案(在 cmets 中注明)将数组分成两个相等的部分;打电话给split2。可以使用split2 构建三部分版本。

      1. 向数组中添加一个等于原始数字总和三分之一的新数字。
      2. 使用split2 将数组分成两部分。
      3. 一个部分有添加的编号;删除它。
      4. 使用split2 将另一部分分成两部分。

      【讨论】:

      • 不错的算法,但是如果源数组中所有元素的totalSum 等于0,它就会出现问题。在这种情况下,split2 函数调用毫无意义,因为它将完全拆分所有原始数组和新的“假”元素。
      • @Hit 有趣的点。在实践中,totalSum 为零的情况是微不足道的:您有原始集和两个空集。但是,根据split2 的工作方式,如果盲目遵循我描述的方法,可能无法获得这个简单的解决方案。
      【解决方案6】:

      更新代码:

      我建议的方法如下(代码如下):

      • 创建数据结构(集合等)来表示您需要的输出部分的数量(在您的示例 3 中)
      • 按降序对输入数组进行排序。
      • 遍历输入数组的元素和每个值:
        • 选择一个输出部分来放置值(这应该是当前总和最低的输出部分..)
        • 将值添加到选定的输出部分

      使用上述逻辑,您将始终添加到总价值最低的输出部分(这将有助于保持总价值相似的部分)。

      (在下面的代码示例中,我跳过了数组排序步骤,因为您的示例已经排序)

      代码:

              // the input array
              int[] inputArray = new int[] { 10, 8, 8, 7, 6, 6, 6, 5 };
      
              // the number of parts you want
              int numberOfOutputParts = 3;
      
              // create the part structures
              List<Part> listOfParts = new List<Part>();
      
              for(int i =0; i < numberOfOutputParts; i++)
              {
                  listOfParts.Add(new Part());
              }
      
              // iterate through each input value
              foreach (int value in inputArray)
              {
                  // find the part with the lowest sum
                  int? lowestSumFoundSoFar = null;
                  Part lowestValuePartSoFar = null;
      
                  foreach(Part partToCheck in listOfParts)
                  {
                      if (lowestSumFoundSoFar == null || partToCheck.CurrentSum < lowestSumFoundSoFar)
                      {
                          lowestSumFoundSoFar = partToCheck.CurrentSum;
                          lowestValuePartSoFar = partToCheck;
                      }
                  }
      
                  // add the value to that Part
                  lowestValuePartSoFar.AddValue(value);
              }
      

      上面使用的 Part 类的代码(虽然你可以使用更好的代码如下):

      public class Part
      {
          public List<int> Values
          {
              get;
              set;
          }
      
          public int CurrentSum
          {
              get;
              set;
          }
      
          /// <summary>
          /// Default Constructpr
          /// </summary>
          public Part()
          {
              Values = new List<int>();
          }
      
          public void AddValue(int value)
          {
              Values.Add(value);
              CurrentSum += value;
          }
      }
      

      【讨论】:

      • @vuonghien:恭喜您解决了问题。你为什么不投票给与会者^^。很长的源代码....
      猜你喜欢
      • 1970-01-01
      • 2022-11-19
      • 2015-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多