ackb đúng rằng các giải pháp dựa trên vectơ này không thể được coi là trung bình thực của các góc, chúng chỉ là trung bình của các đối tác vectơ đơn vị. Tuy nhiên, giải pháp được đề xuất của ackb không xuất hiện với âm thanh toán học.
Sau đây là một giải pháp có nguồn gốc toán học từ mục tiêu giảm thiểu (góc [i] - avgAngle) ^ 2 (trong đó sự khác biệt được điều chỉnh nếu cần thiết), làm cho nó trở thành trung bình số học thực sự của các góc.
Đầu tiên, chúng ta cần xem xét chính xác trường hợp nào sự khác biệt giữa các góc khác với sự khác biệt giữa các đối tác số bình thường của chúng. Xét các góc x và y, nếu y> = x - 180 và y <= x + 180, thì chúng ta có thể sử dụng trực tiếp sự khác biệt (xy). Mặt khác, nếu điều kiện đầu tiên không được đáp ứng thì chúng ta phải sử dụng (y + 360) trong phép tính thay vì y. Tương ứng, nếu điều kiện thứ hai không được đáp ứng thì chúng ta phải sử dụng (y-360) thay vì y. Do phương trình của đường cong, chúng tôi chỉ giảm thiểu các thay đổi tại các điểm mà các bất đẳng thức này thay đổi từ đúng thành sai hoặc ngược lại, chúng tôi có thể tách phạm vi [0,360) đầy đủ thành một tập hợp các phân đoạn, cách nhau bởi các điểm này. Sau đó, chúng ta chỉ cần tìm mức tối thiểu của mỗi phân khúc này và sau đó là mức tối thiểu của mỗi phân khúc, là mức trung bình.
Đây là một hình ảnh chứng minh nơi các vấn đề xảy ra trong việc tính toán sự khác biệt góc. Nếu x nằm trong vùng màu xám thì sẽ có vấn đề.
Để giảm thiểu một biến, tùy thuộc vào đường cong, chúng ta có thể lấy đạo hàm của những gì chúng ta muốn giảm thiểu và sau đó chúng ta tìm thấy bước ngoặt (đó là nơi đạo hàm = 0).
Ở đây chúng tôi sẽ áp dụng ý tưởng giảm thiểu chênh lệch bình phương để rút ra công thức trung bình số học phổ biến: sum (a [i]) / n. Đường cong y = sum ((a [i] -x) ^ 2) có thể được thu nhỏ theo cách này:
y = sum((a[i]-x)^2)
= sum(a[i]^2 - 2*a[i]*x + x^2)
= sum(a[i]^2) - 2*x*sum(a[i]) + n*x^2
dy\dx = -2*sum(a[i]) + 2*n*x
for dy/dx = 0:
-2*sum(a[i]) + 2*n*x = 0
-> n*x = sum(a[i])
-> x = sum(a[i])/n
Bây giờ áp dụng nó cho các đường cong với sự khác biệt được điều chỉnh của chúng tôi:
b = tập con của a trong đó chênh lệch (góc) chính xác a [i] -xc = tập con của a trong đó chênh lệch (góc) đúng (a [i] -360) -x cn = size của cd = tập con của a trong đó đúng (góc) khác biệt (a [i] +360) -x dn = kích thước của d
y = sum((b[i]-x)^2) + sum(((c[i]-360)-b)^2) + sum(((d[i]+360)-c)^2)
= sum(b[i]^2 - 2*b[i]*x + x^2)
+ sum((c[i]-360)^2 - 2*(c[i]-360)*x + x^2)
+ sum((d[i]+360)^2 - 2*(d[i]+360)*x + x^2)
= sum(b[i]^2) - 2*x*sum(b[i])
+ sum((c[i]-360)^2) - 2*x*(sum(c[i]) - 360*cn)
+ sum((d[i]+360)^2) - 2*x*(sum(d[i]) + 360*dn)
+ n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
- 2*x*(sum(b[i]) + sum(c[i]) + sum(d[i]))
- 2*x*(360*dn - 360*cn)
+ n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
- 2*x*sum(x[i])
- 2*x*360*(dn - cn)
+ n*x^2
dy/dx = 2*n*x - 2*sum(x[i]) - 2*360*(dn - cn)
for dy/dx = 0:
2*n*x - 2*sum(x[i]) - 2*360*(dn - cn) = 0
n*x = sum(x[i]) + 360*(dn - cn)
x = (sum(x[i]) + 360*(dn - cn))/n
Chỉ riêng điều này là không đủ để đạt mức tối thiểu, trong khi nó hoạt động với các giá trị bình thường, có một tập hợp không giới hạn, do đó, kết quả chắc chắn sẽ nằm trong phạm vi của tập hợp và do đó là hợp lệ. Chúng tôi cần tối thiểu trong một phạm vi (được xác định bởi phân khúc). Nếu mức tối thiểu nhỏ hơn giới hạn dưới của phân khúc của chúng tôi thì mức tối thiểu của phân khúc đó phải ở giới hạn dưới (vì các đường cong bậc hai chỉ có 1 điểm quay đầu) và nếu mức tối thiểu lớn hơn giới hạn trên của phân khúc của chúng tôi thì mức tối thiểu của phân khúc là giới hạn trên. Sau khi chúng tôi có mức tối thiểu cho mỗi phân đoạn, chúng tôi chỉ cần tìm một phân đoạn có giá trị thấp nhất cho những gì chúng tôi thu nhỏ (sum ((b [i] -x) ^ 2) + sum (((c [i] -360 ) -b) ^ 2) + tổng (((d [i] +360) -c) ^ 2)).
Đây là hình ảnh cho đường cong, cho thấy nó thay đổi như thế nào tại các điểm có x = (a [i] +180)% 360. Tập dữ liệu được đề cập là {65,92,230,320,250}.
Đây là một triển khai thuật toán trong Java, bao gồm một số tối ưu hóa, độ phức tạp của nó là O (nlogn). Nó có thể được giảm xuống O (n) nếu bạn thay thế sắp xếp dựa trên so sánh bằng một loại không dựa trên so sánh, chẳng hạn như sắp xếp cơ số.
static double varnc(double _mean, int _n, double _sumX, double _sumSqrX)
{
return _mean*(_n*_mean - 2*_sumX) + _sumSqrX;
}
//with lower correction
static double varlc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
+ 2*360*_sumC + _nc*(-2*360*_mean + 360*360);
}
//with upper correction
static double varuc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
- 2*360*_sumC + _nc*(2*360*_mean + 360*360);
}
static double[] averageAngles(double[] _angles)
{
double sumAngles;
double sumSqrAngles;
double[] lowerAngles;
double[] upperAngles;
{
List<Double> lowerAngles_ = new LinkedList<Double>();
List<Double> upperAngles_ = new LinkedList<Double>();
sumAngles = 0;
sumSqrAngles = 0;
for(double angle : _angles)
{
sumAngles += angle;
sumSqrAngles += angle*angle;
if(angle < 180)
lowerAngles_.add(angle);
else if(angle > 180)
upperAngles_.add(angle);
}
Collections.sort(lowerAngles_);
Collections.sort(upperAngles_,Collections.reverseOrder());
lowerAngles = new double[lowerAngles_.size()];
Iterator<Double> lowerAnglesIter = lowerAngles_.iterator();
for(int i = 0; i < lowerAngles_.size(); i++)
lowerAngles[i] = lowerAnglesIter.next();
upperAngles = new double[upperAngles_.size()];
Iterator<Double> upperAnglesIter = upperAngles_.iterator();
for(int i = 0; i < upperAngles_.size(); i++)
upperAngles[i] = upperAnglesIter.next();
}
List<Double> averageAngles = new LinkedList<Double>();
averageAngles.add(180d);
double variance = varnc(180,_angles.length,sumAngles,sumSqrAngles);
double lowerBound = 180;
double sumLC = 0;
for(int i = 0; i < lowerAngles.length; i++)
{
//get average for a segment based on minimum
double testAverageAngle = (sumAngles + 360*i)/_angles.length;
//minimum is outside segment range (therefore not directly relevant)
//since it is greater than lowerAngles[i], the minimum for the segment
//must lie on the boundary lowerAngles[i]
if(testAverageAngle > lowerAngles[i]+180)
testAverageAngle = lowerAngles[i];
if(testAverageAngle > lowerBound)
{
double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumLC);
if(testVariance < variance)
{
averageAngles.clear();
averageAngles.add(testAverageAngle);
variance = testVariance;
}
else if(testVariance == variance)
averageAngles.add(testAverageAngle);
}
lowerBound = lowerAngles[i];
sumLC += lowerAngles[i];
}
//Test last segment
{
//get average for a segment based on minimum
double testAverageAngle = (sumAngles + 360*lowerAngles.length)/_angles.length;
//minimum is inside segment range
//we will test average 0 (360) later
if(testAverageAngle < 360 && testAverageAngle > lowerBound)
{
double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,lowerAngles.length,sumLC);
if(testVariance < variance)
{
averageAngles.clear();
averageAngles.add(testAverageAngle);
variance = testVariance;
}
else if(testVariance == variance)
averageAngles.add(testAverageAngle);
}
}
double upperBound = 180;
double sumUC = 0;
for(int i = 0; i < upperAngles.length; i++)
{
//get average for a segment based on minimum
double testAverageAngle = (sumAngles - 360*i)/_angles.length;
//minimum is outside segment range (therefore not directly relevant)
//since it is greater than lowerAngles[i], the minimum for the segment
//must lie on the boundary lowerAngles[i]
if(testAverageAngle < upperAngles[i]-180)
testAverageAngle = upperAngles[i];
if(testAverageAngle < upperBound)
{
double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumUC);
if(testVariance < variance)
{
averageAngles.clear();
averageAngles.add(testAverageAngle);
variance = testVariance;
}
else if(testVariance == variance)
averageAngles.add(testAverageAngle);
}
upperBound = upperAngles[i];
sumUC += upperBound;
}
//Test last segment
{
//get average for a segment based on minimum
double testAverageAngle = (sumAngles - 360*upperAngles.length)/_angles.length;
//minimum is inside segment range
//we test average 0 (360) now
if(testAverageAngle < 0)
testAverageAngle = 0;
if(testAverageAngle < upperBound)
{
double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,upperAngles.length,sumUC);
if(testVariance < variance)
{
averageAngles.clear();
averageAngles.add(testAverageAngle);
variance = testVariance;
}
else if(testVariance == variance)
averageAngles.add(testAverageAngle);
}
}
double[] averageAngles_ = new double[averageAngles.size()];
Iterator<Double> averageAnglesIter = averageAngles.iterator();
for(int i = 0; i < averageAngles_.length; i++)
averageAngles_[i] = averageAnglesIter.next();
return averageAngles_;
}
Giá trị trung bình số học của một tập hợp các góc có thể không phù hợp với ý tưởng trực quan của bạn về mức trung bình nên là bao nhiêu. Ví dụ: giá trị trung bình số học của tập {179,179,0,181,181} là 216 (và 144). Câu trả lời bạn nghĩ ngay đến có lẽ là 180, tuy nhiên ai cũng biết rằng trung bình số học bị ảnh hưởng nặng nề bởi các giá trị cạnh. Bạn cũng nên nhớ rằng các góc không phải là vectơ, hấp dẫn như đôi khi có thể xử lý các góc.
Thuật toán này tất nhiên cũng áp dụng cho tất cả các đại lượng tuân theo số học mô-đun (với sự điều chỉnh tối thiểu), chẳng hạn như thời gian trong ngày.
Tôi cũng muốn nhấn mạnh rằng mặc dù đây là trung bình thực của các góc, không giống như các giải pháp vectơ, điều đó không nhất thiết có nghĩa là đó là giải pháp bạn nên sử dụng, trung bình của các vectơ đơn vị tương ứng có thể là giá trị bạn thực sự nên được sử dụng.