18

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

“Ask A Scientist”网站提供了这个答案

抛物线的一般形式由以下等式给出: 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++ 中执行此计算的实际代码。有人吗?

4

8 回答 8

33

谢谢大卫,我将您的伪代码转换为以下 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);
}

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

于 2009-04-04T21:16:09.110 回答
25

这实际上只是一个简单的线性代数问题,因此您可以象征性地进行计算。当您代入三个点的 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++ 绑定)。

在任何情况下,一旦通过这些公式计算得到、 和的值A,您只需将它们代入问题中给出的表达式和,即可计算顶点的坐标。1BC-B / 2AC - B^2/4A

请注意,如果原始三个点的坐标会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。还有许多其他情况可能会从这样的“技巧”中受益,例如 ifA非常大或非常小,或者 ifC几乎等于,B^2 / 4A因此它们的差异非常小,但我认为这些情况变化很大,可以进行完整的讨论最好留给个案跟进问题。

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


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

于 2009-04-04T20:45:54.427 回答
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 矩阵非常容易,所以这应该不会太难。

于 2009-04-04T20:42:10.597 回答
2

这是实现@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
于 2014-05-02T22:28:13.623 回答
2

我做了一些类似于@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
于 2018-02-09T11:38:56.370 回答
1
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)
于 2017-04-13T13:45:26.217 回答
0

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

| 中号11中号12中号13 | | 一个 | | Z 1 |
| 中号21中号22中号23 | * | 乙| = | Z 2 |
| 中号31中号32中号33 | | C | | Z 3 |

其中 M 11 = x 1 2 , M 12 = x 1 , M 13 = 1, Z 1 = y 1,对于其他两行,使用 (x2, y2) 和 (x3, y3) 代替 (x1, y1)。

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

于 2009-04-04T20:39:58.450 回答
0

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实时运行

于 2018-07-18T14:20:24.830 回答