【问题标题】:How to calculate the vertex of a parabola given three points给定三个点如何计算抛物线的顶点
【发布时间】:2009-04-04 20:30:45
【问题描述】:

我有三个 X/Y 点形成抛物线。我只需要计算通过这三个点的抛物线的顶点是多少。最好是一种快速的方法,因为我必须做很多这些计算!

“Ask A Scientist”网站提供this answer

抛物线的一般形式由以下等式给出: A * x^2 + B * x + C = y 其中 A、B 和 C 是任意实数常数。您有三对点,它们是 (x,y) 有序对。将每个点的 x 和 y 值代入抛物线方程。你会得到三个未知数的三个线性方程,三个常数。然后,您可以轻松地求解 A、B 和 C 值的三个方程组,并且您将获得与 3 个点相交的抛物线方程。顶点是一阶导数为0的地方,一点代数给出:( -B/2A , C - B^2/4A ) 为顶点。

很高兴看到在 C# 或 C++ 中执行此计算的实际代码。有人吗?

【问题讨论】:

    标签: curve-fitting


    【解决方案1】:

    感谢 David,我将您的伪代码转换为以下 C# 代码:

    public static void CalcParabolaVertex(int x1, int y1, int x2, int y2, int x3, int y3, out double xv, out double yv)
    {
        double denom = (x1 - x2) * (x1 - x3) * (x2 - x3);
        double A     = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
        double B     = (x3*x3 * (y1 - y2) + x2*x2 * (y3 - y1) + x1*x1 * (y2 - y3)) / denom;
        double C     = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
    
        xv = -B / (2*A);
        yv = C - B*B / (4*A);
    }
    

    这就是我想要的。抛物线顶点的简单计算。稍后我会处理整数溢出。

    【讨论】:

    【解决方案2】:

    这实际上只是一个简单的线性代数问题,因此您可以进行符号计算。当您代入三个点的 x 和 y 值时,您将得到三个未知数的三个线性方程。

    A x1^2 + B x1 + C = y1
    A x2^2 + B x2 + C = y2
    A x3^2 + B x3 + C = y3
    

    解决这个问题的直接方法是反转矩阵

    x1^2  x1  1
    x2^2  x2  1
    x3^2  x3  1
    

    乘以向量

    y1
    y2
    y3
    

    这样做的结果是……好吧,并不是那么简单 ;-) 我在 Mathematica 中做过,下面是伪代码中的公式:

    denom = (x1 - x2)(x1 - x3)(x2 - x3)
    A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
    B = (x3^2 * (y1 - y2) + x2^2 * (y3 - y1) + x1^2 * (y2 - y3)) / denom
    C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom
    

    或者,如果您想以数值方式进行矩阵数学运算,您通常会求助于线性代数系统(例如 ATLAS,尽管我不确定它是否具有 C#/C++ 绑定)。

    在任何情况下,一旦你得到ABC 的值(由这些公式计算得出),你只需要将它们代入问题中给出的表达式-B / 2A 和@ 987654331@,计算顶点坐标。1

    请注意,如果原始三个点的坐标使denom 成为一个非常大或非常小的数字,则直接进行计算可能会出现严重的数值错误。在这种情况下,最好稍微修改一下,以避免被分母除以它们无论如何都会抵消:

    denom = (x1 - x2)(x1 - x3)(x2 - x3)
    a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2))
    b = (x3^2 * (y1 - y2) + x2^2 * (y3 - y1) + x1^2 * (y2 - y3))
    c = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3)
    

    然后顶点的坐标是-b / 2a(c - b^2 / 4a) / denom。还有许多其他情况可能会从这样的“技巧”中受益,例如A 非常大或非常小,或者C 几乎等于B^2 / 4A,因此它们的差异非常小,但我认为这些情况变化很大,最好留出完整的讨论来处理逐个案例的后续问题。

    将所有这些转换为您选择的语言的代码留给读者作为练习。 (在任何使用标准中缀运算符语法的语言中,它应该是非常简单的,例如AZDean showed in C#。)


    1在答案的初始版本中,我认为这很明显,但似乎有很多人喜欢明确提及它。

    【讨论】:

      【解决方案3】:

      直接代入得到以下三个方程:

      A*x1^2+B*x1+C=y1
      A*x2^2+B*x2+C=y2
      A*x3^2+B*x3+C=y3
      

      您可以通过注意这等效于矩阵乘积来解决这个问题:

      [x1^2 x1 1] [A]   [y1]
      |x2^2 x2 1|*|B| = |y2|
      [x3^2 x3 1] [C]   [y3]
      

      所以你可以通过反转矩阵并将逆矩阵与右侧的向量相乘得到 A、B 和 C。

      我看到,虽然我一直在发布此 John Rasch 已链接到教程,该教程更深入地实际求解矩阵方程,因此您可以按照这些说明获得答案。反转一个 3x3 矩阵很容易,所以这应该不会太难。

      【讨论】:

        【解决方案4】:

        这是实现@david-z 和@AZDean 解决方案的Fortran 代码:

        subroutine parabola_vertex(x1, y1, x2, y2, x3, y3, xv, yv)
        real(dp), intent(in) :: x1, y1, x2, y2, x3, y3
        real(dp), intent(out) :: xv, yv
        real(dp) :: denom, A, B, C
        denom = (x1 - x2) * (x1 - x3) * (x2 - x3)
        A     = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
        B     = (x3**2 * (y1 - y2) + x2**2 * (y3 - y1) + x1**2 * (y2 - y3)) / denom
        C     = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + &
                    x1 * x2 * (x1 - x2) * y3) / denom
        xv = -B / (2*A)
        yv = C - B**2 / (4*A)
        end subroutine
        

        【讨论】:

          【解决方案5】:

          我做了类似于@piSHOCK 的回答,同样基于@AZDean 的代码。如果你需要大量运行它(或者像我一样在 Matlab 中使用它),这可能是最快的。

          我的假设是x1 == -1, x2 == 0, x3 == 1

          a = y2 - ( y1 + y3) / 2   % opposite signal compared to the original definition of A
          b = (y3 - y1) / 4         % half of the originally defined B
          
          xExtr = b / a
          yExtr = y2 + b * xExtr    % which is equal to y2 + b*b / a
          

          【讨论】:

          • 最后一个 yExtr 应该是 xExtr。
          【解决方案6】:
          def vertex(x1,x2,x3,y1,y2,y3):
              '''Given three pairs of (x,y) points return the vertex of the
                   parabola passing through the points. Vectorized and common expression reduced.'''
              #Define a sequence of sub expressions to reduce redundant flops
              x0 = 1/x2
              x4 = x1 - x2
              x5 = 1/x4
              x6 = x1**2
              x7 = 1/x6
              x8 = x2**2
              x9 = -x7*x8 + 1
              x10 = x0*x1*x5*x9
              x11 = 1/x1
              x12 = x3**2
              x13 = x11*x12
              x14 = 1/(x0*x13 - x0*x3 - x11*x3 + 1)
              x15 = x14*y3
              x16 = x10*x15
              x17 = x0*x5
              x18 = -x13 + x3
              x19 = y2*(x1*x17 + x14*x18*x6*x9/(x4**2*x8))
              x20 = x2*x5
              x21 = x11*x20
              x22 = x14*(-x12*x7 + x18*x21)
              x23 = y1*(-x10*x22 - x21)
              x24 = x16/2 - x19/2 - x23/2
              x25 = -x17*x9 + x7
              x26 = x0*x1*x14*x18*x5
              x27 = 1/(-x15*x25 + y1*(x20*x7 - x22*x25 + x7) + y2*(-x17 + x25*x26))
              x28 = x24*x27
              return x28,x15 + x22*y1 + x24**2*x27 - x26*y2 + x28*(-x16 + x19 + x23)
          

          【讨论】:

          • +1 它可以工作,除非 x2=0。此外,如果这是 Python 代码(语法是有效的 Python),我建议您使用像 1./x 这样的除法而不是 1/x 以确保不会发生整数除法。
          【解决方案7】:

          这闻起来像家庭作业。 “问一个科学家”是正确的。假设您的 3 个点是 (x1, y1)、(x2, y2) 和 (x3, y3)。然后,你得到三个线性方程:

          | M11 M12 M13 | |一个 | | Z1 | | M21 M22 M23 | * |乙| = | Z2 | | M31 M32 M33 | | C | | Z3 |

          其中 M11 = x12,M12 = x1, M13 = 1, Z1 = y1,对于其他两行同样使用 (x2, y2) 和 (x3 , y3) 代替 (x1, y1)。

          求解这个由 3 个方程组成的系统将为您提供 A、B 和 C 的解。

          【讨论】:

            【解决方案8】:

            https://ideone.com/y0SxKU运行

            #include <iostream>
            using namespace std;
            // calculate the vertex of a parabola given three points
            // https://stackoverflow.com/q/717762/16582
            
            // @AZDean implementation with given x values
            
            void CalcParabolaVertex(int x1, int y1, int x2, int y2, int x3, int y3, double& xv,  double& yv)
            {
                double denom = (x1 - x2) * (x1 - x3) * (x2 - x3);
                double A     = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
                double B     = (x3*x3 * (y1 - y2) + x2*x2 * (y3 - y1) + x1*x1 * (y2 - y3)) / denom;
                double C     = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
            
                xv = -B / (2*A);
                yv = C - B*B / (4*A);
            }
            
            // @piSHOCK immplementation assuming regular x values ( wrong!!! )
            
            void CalcParabolaVertex2( int y1, int y2, int y3, double& xv,  double& yv)
            {
            double d1 = y1 - y2;
            double d2 = y1 - y3;
            
            double a =    -d1 + 0.5 * d2;
            double b = 2 * d1 - 0.5 * d2;
            double c = -y1;
            
            xv =    -0.5      * b / a;
            yv = c - 0.25 * b * b / a;  
            }
            
            // corrected immplementation assuming regular x values
            
            void CalcParabolaVertex3( int y1, int y2, int y3, double& xv,  double& yv)
            {
            double d1 = y1 - y2;
            double d2 = y1 - y3;
            
            double a = d1 - 0.5 * d2;
            double b = -2 * d1 + 0.5 * d2;
            double c = y1;
            
            xv =    -0.5      * b / a;
            yv = c - 0.25 * b * b / a;  
            }
            
            
            int main() {
                double xv, yv;
                CalcParabolaVertex( 0, 100, 1, 500, 2, 200, xv, yv );
                cout << xv <<" "<< yv << "\n";
                CalcParabolaVertex2( 100, 500, 200, xv, yv );
                cout << xv <<" "<< yv << "\n";
                CalcParabolaVertex3( 100, 500, 200, xv, yv );
                cout << xv <<" "<< yv << "\n";
                return 0;
            }
            

            我为负峰值添加了几个单元测试:在https://ideone.com/WGK90S 实时运行

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-12-19
              • 2014-08-31
              • 2014-09-25
              • 2021-05-21
              • 2013-02-15
              • 1970-01-01
              相关资源
              最近更新 更多