简体   繁体   English

生成满足给定约束条件的N维空间中所有点的算法

[英]Algorithm to generate all points in an N-dimensional space satisfying a given set of constraints

I encountered this interesting problem a few weeks ago: Given an n-dimensional space and a "step size" value that lies between (0,1], generate all points that satisfy the following constraints: 几周前,我遇到了一个有趣的问题:给定一个n维空间和介于(0,1]之间的“步长”值,请生成满足以下约束的所有点:

  • The value of each dimension of a point is a multiple of the "step size" 点的每个维度的值是“步长”的倍数
  • The value of each dimension of a point ranges between 0 and 1 inclusive. 点的每个维的值的范围在0到1之间(含0和1)。 For example, a 2D point (x,y) should satisfy 0=<x,y<= 1 例如,一个2D点(x,y)应该满足0 = <x,y <= 1
  • The sum of values of all dimensions must be equal to 1 (Updated) 所有维度的值之和必须等于1 (更新)

Example

Input: 输入:
stepSize = 0.5 and numDimensions = 3 (ie, 3D space) stepSize = 0.5并且numDimensions = 3(即3D空间)

Output: 输出:
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

Since we need to find all possible points, I thought of a recursive solution. 由于我们需要找到所有可能的要点,因此我想到了一个递归解决方案。 Here is my code: 这是我的代码:

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(" ");
        }
    }
}

This seems to work correctly for a few test cases that I tried. 对于我尝试的一些测试用例,这似乎可以正常工作。 Example output for (stepSize=0.5f and numDimensions=3): (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,

I have a few questions: 我有几个问题:

  1. Is my approach to solving this problem correct? 我解决这个问题的方法正确吗?
  2. What is the exact time complexity of my solution? 我的解决方案的确切时间复杂度是多少? I claimed that it was exponential but was unable to correctly articulate the time complexity in terms of number of dimensions/step size/number of points. 我声称它是指数级的,但是无法正确地表达时间复杂性,涉及的维数/步长/点数。 What would be the best approach to reason about time complexities for the above problem and for recursive algorithms like these in general? 对于上述问题以及类似的递归算法,时间推理的最佳方法是什么?
  3. If my understanding of the time complexity being exponential is correct, is there a more efficient algorithm to solve this particular problem? 如果我对呈指数形式的时间复杂性的理解是正确的,那么是否有更有效的算法来解决这个特定问题?

EDIT I missed a third constraint: Sum of all values of a dimension must sum to 1.0 (Apologies, I forgot to mention this earlier). 编辑我错过了第三个约束:一个维度的所有值的总和必须为1.0(对不起,我忘记了前面提到的内容)。

You have V = ValuesCount = 1 + 1/stepSize possible values for every dimension and nD dimensions. 对于每个维和nD维,您都有V = ValuesCount = 1 + 1/stepSize可能的值。 There are V points in 1D, V*V points in 2D, V^3 in 3D, V^nD points in nD-dimensional space. 一维中有V点,二维中有V * V点,3D中有V ^ 3,nD维空间中有V^nD个点。

Note that you can generate all point coordinates in simple for-cycle 请注意,您可以在简单的for循环中生成所有点坐标

  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)

Example for your V=3, nD=3 case: 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. Is my approach to solving this problem correct? 我解决这个问题的方法正确吗?

I've not looked at your code, but your example output is missing several points. 我没有看过您的代码,但是您的示例输出缺少几点。 There should be 27. 应该有27个。

Note that using a for loop with floating point numbers can lead to problems, owing to the accumulated error in the loop variable. 请注意,由于循环变量中累积的错误,使用带浮点数的for循环可能会导致问题。 It is better to loop on integers, and then divide inside the loop: 最好对整数进行循环,然后在循环内除法:

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

instead of 代替

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

( Compare the output of the two - notice inaccuracies in the second case as well as printing one fewer line) 比较两者的输出 -在第二种情况下注意不正确,以及少打印一行)

  1. is there a more efficient algorithm to solve this particular problem? 有没有更有效的算法来解决这个特定问题?

There are 1 + 1/stepSize values for each coordinate; 每个坐标都有1 + 1/stepSize值; there are numDimensions coordinates. numDimensions座标。 Hence, there should be (1 + 1/stepSize)^numDimensions distinct points in that space. 因此,该空间中应该有(1 + 1/stepSize)^numDimensions不同的点。

Hence, the optimal complexity of iterating through all the points is O((1 + 1/stepSize)^numDimensions) . 因此,遍历所有点的最佳复杂度为O((1 + 1/stepSize)^numDimensions)

Now, after edit, this is absolutely different problem. 现在,在编辑之后,这是完全不同的问题。

This is combinatorial task of generation of all partitions of number N into N+1 parts when order of parts is important ( compositions ). 当部分的顺序很重要时( 组成 ),这是将N个所有分区生成为N + 1个部分的组合任务。 Delphi code (divide values by N to get your 'coordinates'). Delphi代码(将值除以N得到“坐标”)。

Note that there are C(2n,n) such compositions (C(6,3)=20), where C - number of combinations. 注意,有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;

output for N=2 and N=3 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 

A friend of mine provided a great solution to this problem. 我的一个朋友为这个问题提供了很好的解决方案。 As some people here, mentioned in their answers, it is a problem that boils down to generating all the ways of writing a integer 'm' as a sum of 'k' non-negative integers. 正如这里的某些人在回答中提到的那样,这个问题归结为生成所有形式写整数“ m”作为“ k”个非负整数之和的方法。 Here is a link detailing this problem. 这是详细说明此问题的链接

Incorporating Andy's feedback of working with integers, here is the updated java code with some comments. 结合了Andy使用整数的反馈,这是带有一些注释的更新的Java代码。 Please not this is the java adaptation of the solution provided by my friend: 请不要这样,这是我朋友提供的解决方案的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. Turns out my approach (and my original code) was wrong, as I was not computing all points correctly - the code did not compute points that had steps different from each other, example: [0.0 0.2 0.8 0.0]. 事实证明我的方法(和我原来的代码)是错误的,因为我没有正确计算所有点-的代码没有计算该有步骤彼此,例如不同点:0.0 0.2 0.8 0.0]。
  2. Time complexity: (as mentioned in the math exchange link ): Its equal to the number of possible points that is given by: C(n+k-1, k-1) where n = (1/stepSize) and k = numDimensions. 时间复杂度:(如数学交换链接中所述 ):它等于以下条件所给出的可能点数:C(n + k-1,k-1)其中n =(1 / stepSize)和k = numDimensions 。 An example: for stepSize = 0.2 (== 5 as an integer), and numDimensions = 4 , the number of points possible is 56 . 例如:对于stepSize = 0.2 (== 5为整数),并且numDimensions = 4 ,可能的点数为56

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM