ackb นั้นถูกต้องว่าโซลูชันแบบเวกเตอร์เหล่านี้ไม่สามารถพิจารณาค่าเฉลี่ยจริงของมุมได้พวกมันเป็นเพียงค่าเฉลี่ยของหน่วยเวกเตอร์หน่วย อย่างไรก็ตามวิธีการแก้ปัญหาที่แนะนำของ ackb นั้นไม่ได้มีเสียงทางคณิตศาสตร์
ต่อไปนี้เป็นวิธีการแก้ปัญหาที่ได้มาทางคณิตศาสตร์จากเป้าหมายของการย่อเล็กสุด (angle [i] - avgAngle) ^ 2 (ซึ่งความแตกต่างได้รับการแก้ไขหากจำเป็น) ซึ่งทำให้มันเป็นค่าเฉลี่ยเลขคณิตจริงของมุม
อันดับแรกเราต้องดูให้แน่ชัดว่ากรณีใดความแตกต่างระหว่างมุมต่างกับความแตกต่างระหว่างจำนวนปกติ พิจารณามุม x และ y ถ้า y> = x - 180 และ y <= x + 180 จากนั้นเราสามารถใช้ความแตกต่าง (xy) โดยตรง มิฉะนั้นหากเงื่อนไขแรกไม่เป็นไปตามนั้นเราจะต้องใช้ (y + 360) ในการคำนวณแทน y หากเงื่อนไขที่สองไม่เป็นไปตามเงื่อนไขเราต้องใช้ (y-360) แทน y เนื่องจากสมการของเส้นโค้งเราจึงลดการเปลี่ยนแปลงเฉพาะจุดที่ความไม่เท่าเทียมกันเหล่านี้เปลี่ยนจากจริงเป็นเท็จหรือในทางกลับกันเราสามารถแยกช่วง [0,360) ทั้งหมดออกเป็นชุดเซ็กเมนต์โดยคั่นด้วยจุดเหล่านี้ จากนั้นเราจะต้องค้นหาค่าต่ำสุดของแต่ละส่วนเหล่านี้แล้วเลือกค่าต่ำสุดของค่าต่ำสุดของแต่ละส่วนซึ่งก็คือค่าเฉลี่ย
นี่คือภาพที่แสดงให้เห็นถึงปัญหาที่เกิดขึ้นในการคำนวณความแตกต่างของมุม หาก x อยู่ในพื้นที่สีเทาแสดงว่ามีปัญหา
ในการลดตัวแปรให้น้อยที่สุดขึ้นอยู่กับเส้นโค้งเราสามารถหาอนุพันธ์ของสิ่งที่เราต้องการย่อให้เล็กที่สุดแล้วหาจุดเปลี่ยน (ซึ่งเป็นที่มาของอนุพันธ์ = 0)
ที่นี่เราจะใช้แนวคิดของการลดความแตกต่างกำลังสองเพื่อให้ได้สูตรทางคณิตศาสตร์ทั่วไป: sum (a [i]) / n เส้นโค้ง y = ผลรวม ((a [i] -x) ^ 2) สามารถย่อให้เล็กสุดด้วยวิธีนี้:
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 = เซตย่อยของจุดที่ความแตกต่างที่ถูกต้อง (มุม) a [i] -xc = ชุดย่อยของตำแหน่งที่ความแตกต่างที่ถูกต้อง (มุม) (a [i] -360) -x cn = ขนาดของ cd = ชุดย่อยของตำแหน่งที่ ความแตกต่างที่ถูกต้อง (เชิงมุม) (a [i] +360) -x dn = ขนาด 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
เพียงอย่างเดียวนี้ไม่เพียงพอที่จะรับค่าต่ำสุดในขณะที่ทำงานกับค่าปกติที่มีชุดที่ไม่ได้ จำกัด ดังนั้นผลลัพธ์จะอยู่ในช่วงของชุดและแน่นอนจึงถูกต้อง เราต้องการขั้นต่ำภายในช่วง (กำหนดโดยกลุ่ม) หากค่าต่ำสุดนั้นน้อยกว่าขอบเขตล่างของเซกเมนต์ของเราดังนั้นค่าต่ำสุดของเซกเมนต์นั้นจะต้องอยู่ที่ขอบเขตล่าง (เพราะเส้นโค้งกำลังสองมีเพียง 1 จุดหักเห) และถ้าค่าต่ำสุดมากกว่าขอบเขตบนของเซ็กเมนต์ของเรา ขอบบน หลังจากที่เรามีค่าต่ำสุดสำหรับแต่ละเซกเมนต์เราก็จะหาอันที่มีค่าต่ำสุดสำหรับสิ่งที่เรากำลังย่อให้เล็กสุด (ผลรวม ((b [i] -x) ^ 2) + ผลรวม (((c [i] -360 ) -b) ^ 2) + ผลรวม (((d [i +360) -c) ^ 2))
นี่คือรูปภาพของเส้นโค้งซึ่งแสดงให้เห็นว่ามันเปลี่ยนแปลงไปอย่างไร ณ จุดที่ x = (a [i] +180)% 360 ชุดข้อมูลที่เป็นปัญหาคือ {65,92,230,320,250}
นี่คือการใช้อัลกอริทึมใน Java รวมถึงการเพิ่มประสิทธิภาพบางอย่างความซับซ้อนของมันคือ O (nlogn) มันสามารถลดลงเป็น O (n) หากคุณแทนที่การจัดเรียงตามการเปรียบเทียบด้วยการเรียงลำดับที่ไม่ใช่การเปรียบเทียบเช่นการเรียงตามฐาน
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_;
}
ค่าเฉลี่ยเลขคณิตของชุดของมุมอาจไม่เห็นด้วยกับความคิดของคุณว่าค่าเฉลี่ยควรเป็นเท่าไหร่ ตัวอย่างเช่นค่าเฉลี่ยเลขคณิตของชุด {179,179,0,181,181} คือ 216 (และ 144) คำตอบที่คุณคิดในทันทีอาจเป็น 180 อย่างไรก็ตามเป็นที่ทราบกันดีว่าค่าเฉลี่ยเลขคณิตได้รับผลกระทบอย่างมากจากค่าขอบ คุณควรจำไว้ว่ามุมนั้นไม่ใช่เวกเตอร์, น่าดึงดูดอย่างที่อาจดูเหมือนเมื่อต้องรับมือกับมุมในบางครั้ง
แน่นอนว่าอัลกอริธึมนี้ยังนำไปใช้กับปริมาณทั้งหมดที่เชื่อฟังเลขคณิตแบบโมดูลาร์ (ด้วยการปรับค่าน้อยที่สุด) เช่นเวลาของวัน
ฉันอยากจะย้ำด้วยว่าแม้ว่านี่จะเป็นค่าเฉลี่ยมุมที่แท้จริงซึ่งแตกต่างจากวิธีแก้ปัญหาแบบเวกเตอร์ซึ่งไม่ได้แปลว่าเป็นวิธีแก้ปัญหาที่คุณควรใช้ค่าเฉลี่ยของเวกเตอร์หน่วยที่สอดคล้องกันอาจเป็นค่าที่คุณจริง ๆ ควรจะใช้