用于生成满足给定约束集的N维空间中的所有点的算法

时间:2016-05-04 07:35:14

标签: java algorithm recursion time-complexity

几周前我遇到了这个有趣的问题:给定一个n维空间和一个"步长"位于(0,1)之间的值,生成满足以下约束的所有点:

  • 点的每个维度的值是"步长的倍数"
  • 点的每个维度的值介于0和1之间(包括0和1)。例如,2D点(x,y)应满足0 =< x,y< = 1
  • 所有维度的值之和必须等于1 (已更新)

示例

输入:
stepSize = 0.5和numDimensions = 3(即3D空间)

输出:
 0.0, 0.0, 1.0 0.0, 0.5, 0.5 0.0, 1.0, 0.0 0.5, 0.0, 0.5 0.5, 0.5, 0.0 1.0, 0.0, 0.0

由于我们需要找到所有可能的点,我想到了递归解决方案。这是我的代码:

class PointEnumeration {
    static class Point {
        List<Float> dimensions; //a list of float where index i is the (i+1)'th dimension 

        Point(Point p) {
            this.dimensions = new ArrayList<>();
            this.dimensions.addAll(p.dimensions);
        }

        Point(int size) {
            this.dimensions = new ArrayList<>();
            for(int i = 0; i < size; i++){
                //Initialize all dimensions to 0.0f
                this.dimensions.add(0.0f);
            }
        }

        void incr(int pos, float i) {
            float val = dimensions.get(pos);
            dimensions.set(pos, val + i);
        }

        void set(int pos, float i) {
            dimensions.set(pos, i);
        }


        float get(int pos){
            return dimensions.get(pos);
        }
    }


    static List<Point> findPoints(float stepSize, int numDim) {
        if (stepSize > 1) {
            return new ArrayList<>();
        }
        List<Point> res = new ArrayList<>();
        for(float i = stepSize; i <= 1; i+=stepSize) {
            findPointsHelper(i, numDim, 1.0f, 0, new Point(numDim), res);
        }
        return res;
    }

    static void findPointsHelper(float stepSize, int numDim, float sum, int start, Point curr, List<Point> res) {
        if (sum == 0.0) {
            res.add(new Point(curr));
            return;
        }

        for (int i = start; i < numDim; i++) {
            float temp = sum;
            float val = curr.get(i);
            curr.incr(i, stepSize);
            findPointsHelper(stepSize, numDim, sum - stepSize, i + 1, curr, res);
            curr.set(i, val);
            sum = temp;
        }
    }



    public static void main(String[] args) { 
        List<Point> res = findPoints(0.25f, 4); //Tried 1.0f, 3 and 0.5f, 3 as well
        for (Point p : res) {
            for (Float coord : p.dimensions) {
                System.out.print(String.valueOf(coord) + ", ");
            }
            System.out.println(" ");
        }
    }
}

这似乎适用于我尝试过的一些测试用例。 (stepSize = 0.5f和numDimensions = 3)的示例输出:
0.5, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.5, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0,

我有几个问题:

  1. 我解决这个问题的方法是否正确?
  2. 我的解决方案的确切时间复杂度是多少?我声称这是指数级的,但无法根据维数/步长/点数正确地表达时间复杂度。什么是推理上述问题的时间复杂性和一般的递归算法的最佳方法?
  3. 如果我对指数时间复杂度的理解是正确的,是否有更有效的算法来解决这个特殊问题?
  4. 修改 我错过了第三个约束:维度的所有值的总和必须总和为1.0(道歉,我之前忘记提及)。

4 个答案:

答案 0 :(得分:3)

每个维度和V = ValuesCount = 1 + 1/stepSize维度都有nD个可能的值。 1D中有V点,2D中有V * V点,3D中有V ^ 3,nD维空间中有V^nD个点。

请注意,您可以在简单的for-cycle

中生成所有点坐标
  for k = 0..V^nD - 1 
      represent k in V-ary number system
      m-th digit of k is coordinate of the k-th point in m-th dimension   
     (divide by (V-1) to normalize to range 0..1)

你的V = 3,nD = 3案例的例子:

  k = 15(dec) = 120(trinary)
  first (right) digit is 0, second is 2, third is 1
  coordinates (0.0, 1.0, 0.5)

答案 1 :(得分:2)

  
      
  1. 我解决这个问题的方法是否正确?
  2.   

我没有查看您的代码,但您的示例输出缺少几点。应该有27个。

请注意,由于循环变量中累积的错误,使用带浮点数的for循环可能会导致问题。最好循环整数,然后在循环内部划分:

for (int i = 0; i <= 9; ++i) {
  System.out.println(i / 9.0);
}

而不是

for (double i = 0; i <= 1; i += 1.0 / 9.0) {
  System.out.println(i);
}

Compare the output of the two - 注意第二种情况下的不准确以及少打印一行)

  
      
  1. 是否有更有效的算法来解决这个特殊问题?
  2.   

每个坐标都有1 + 1/stepSize个值;有numDimensions个坐标。因此,该空间应该有(1 + 1/stepSize)^numDimensions个不同的点。

因此,迭代所有点的最佳复杂度是O((1 + 1/stepSize)^numDimensions)

答案 2 :(得分:0)

现在,在编辑之后,这是绝对不同的问题。

当部件的顺序很重要(组合)时,这是将N号的所有分区生成为N + 1个部分的组合任务。德尔福代码(将值除以N得到你的&#39;坐标&#39;)。

注意有C(2n,n)这样的成分(C(6,3)= 20),其中C - 组合数。

var
  Cmp: array of Integer;
  N, NP, first, nonzero: Integer;
begin
  N := 3;
  NP := N + 1;
  SetLength(Cmp, NP);  //zero filled
  Cmp[N] := N;
  #output Cmp array
  while Cmp[0] <> N do begin
    first := Cmp[0];
    Cmp[0] := 0;
    nonzero := 1;
    while Cmp[nonzero] = 0 do
      Inc(nonzero);
    Dec(Cmp[nonzero]);
    Cmp[nonzero - 1] := first + 1;
    #output Cmp array
  end;

输出N = 2且N = 3

0 0 2 
0 1 1 
1 0 1 
0 2 0 
1 1 0 
2 0 0 

0 0 0 3 
0 0 1 2 
0 1 0 2 
1 0 0 2 
0 0 2 1 
0 1 1 1 
1 0 1 1 
0 2 0 1 
1 1 0 1 
2 0 0 1 
0 0 3 0 
0 1 2 0 
1 0 2 0 
0 2 1 0 
1 1 1 0 
2 0 1 0 
0 3 0 0 
1 2 0 0 
2 1 0 0 
3 0 0 0 

答案 3 :(得分:0)

我的一位朋友为这个问题提供了很好的解决方案。正如在这里的一些人,在他们的答案中提到的,这是一个问题,归结为产生写一个整数的所有方法&#39; m&#39;作为&#39; k&#39;的总和非负整数。这是一个link详细说明了这个问题。

结合Andy关于使用整数的反馈,这里是更新的java代码和一些注释。请不要这是我的朋友提供的解决方案的java改编:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class PointEnumeration {
    static class Point {
    //a list of integer where index i is the (i+1)'th dimension
    List<Integer> dimensions;

    Point(int step, int numDim){
        this.dimensions = new ArrayList<>();
        for(int i = 0; i < numDim; i++) {
            this.dimensions.add(step);
        }
    }

    Point(int step, Point p){
        this.dimensions = new ArrayList<>();
        this.dimensions.add(step);
        this.dimensions.addAll(p.dimensions);
    }

    int get(int pos) {
        return dimensions.get(pos);
    }
}


private static List<Point> findPoints(int steps, int numDim){
    if(numDim == 1){
        //Only one dimension, add the `steps` to the only dimension
        return Arrays.asList(new Point(steps, 1));
    }

    List<Point> result = new ArrayList<>();

    if(steps == 0){
        //Nothing left, create a point with all zeroes
        return Arrays.asList(new Point(0, numDim));
    }

    //Iterate on the steps
    for(int i = 0; i <= steps; i++){
        //Recurse on the remaining steps and
        //reduce the dimension by 1 (since this dimension will
        // be handled in the next for-each loop)
        List<Point> remaining = findPoints(steps-i, numDim-1);
        for (Point point : remaining) {
            //Append the i'th step to the remaining point
            Point complete = new Point(i, point);
            //This is a complete point for the i'th step
            // and current dimension
            result.add(complete);
        }
    }
    return result;
}

public static void main(String[] args) {
    float stepSize = 0.2f;
    int numDim = 4;

    int steps = (int) Math.ceil(1.0 / stepSize);
    List<Point> res = findPoints(steps, numDim);
    for (Point p : res) {
        for (int coord : p.dimensions) {
            //Convert integer steps to float value
            System.out.print(String.valueOf(coord <= 0 ? 0.0f : (coord / (float) steps)) + ", ");
        }
        System.out.println(" ");
    }
    System.out.println("Total number of points =" + res.size());
}
  1. 原来我的方法(我的原始代码)是错误的,因为我没有正确计算所有点 - 代码确实计算具有彼此不同步骤的点,例如:[0.0 0.2 0.8 0.0]。
  2. 时间复杂度:(如数学交换link中所述):它等于由下式给出的可能点数:C(n + k-1,k-1)其中n =(1 / stepSize)和k = numDimensions。例如:对于 stepSize = 0.2 (= = 5作为整数)和 numDimensions = 4 ,可能的点数为 56