对距参考点的距离进行排序的点数组 |设置 2(使用极角)
给定一个数组arr[]包含N个二维平面点和一个参考点P 0 (a 0 , b 0 ) ,任务是根据这些点与给定点P 0的距离对它们进行排序。
例子:
Input: Points: [(1, 0), (0, -1), (-1, 0), (0, 1)]
Output: After sorting with reference point (0, 0)
[(1, 0), (0, 1), (-1, 0), (0, -1)]
After sorting with reference point (1, 1)
[(0, 1), (-1, 0), (0, -1), (1, 0)]
Input: Points: [(-98, -65), (-65, -60), (65, -52), (-10, -47), (-7, -46), (26, -89), (72, -26), (66, -52), (69, 73), (-64, 0)]
Output: After sorting with reference point (0, 0)
[(69, 73), (-64, 0), (-98, -65), (-65, -60), (-10, -47), (-7, -46), (26, -89), (65, -52), (66, -52), (72, -26)]
方法:解决这个问题的方法是借助极角
The polar angle of a point P1 with respect to an origin point P0 is the angle of the vector P1-P0 in the usual polar coordinate system.
For example, the polar angle of (3, 5) with respect to (2, 4) is the angle of the vector (1, 1), which is 45 degrees or π/4 radians.
In the following diagram, there are two vectors.
- First vector whose endpoint is PA(1, 1) makes an angle 45o with the x-axis.
- Second vector whose endpoint is PB(1, -1) makes an angle 315o with the x-axis.
- The origin point for both the vectors is PO(0, 0).
交叉产品
- 叉积对于计算一个向量是否相对于另一个向量的方向是必要的。
- 考虑任意两点 P 1和 P 2 。
- 如果它们的叉积为正,则 P 1相对于原点顺时针旋转到 P 2 。
- 如果叉积为负,则 P 1从 P 2逆时针方向。
- 在上图中,
PA = (1, 1) and PB = (1, -1)
PA x PB = -2k̂
Hence, PA is anti-clockwise to PB with respect to origin.
- 如果叉积为 0,则 P A与 P B共线。
- 要确定线段 P 0 – P 1与线段 P 0 – P 2 是顺时针还是逆时针方向,其中 P 0是它们的公共端点,请将点P 0平移为原点。
Given P0 (x0, y0), P1 (x1 , y1) and P2 (x3, y3)
(P1 – P0) x (P2 – P0) = (x1 – x0) (y2 – y0) – (x2 – x0) (y1 – y0)
- 如果叉积为正,则 P 0 -P 1顺时针到 P 0 -P 2 ,如果为负,则 P 0 -P 1逆时针到 P 0 -P 2 ,否则它们共线。
- 在上图中,
PA(1, 1), PB(2, 3) and PC(3, 1)
(PB – PA) = (1, 2)
(PC – PA) = (2, 0)
(PB – PA) x (PC – PA) = -4k̂
Hence, vector PA-PB is in anti-clockwise direction to PA-PC with respect to point PA
叉积的缺点:
- 任意两个向量之间的角度在 [0, π) 之间变化。因此,使用叉积,我们只能测量 [0, π) 之间的角度。因此,如果任何两点相对于任何参考点的极角差大于 π,我们将无法获得准确的方向。
- 再次考虑下面给出的图表。如果我们比较 A 和 B 的极角,我们会发现 B 相对于原点 O(0, 0) 的极角比 A 大。但是,如果我们将点 A 和 B 转换为向量并找到它们的方向,我们会发现 A x B = -1,这意味着 A 相对于原点 O 的极角大于 B。这是错误的。这是因为矢量 P B = 315 o和 P A = 45 o的极角。有极角差=270 0大于π。这会导致结果不准确。
克服缺点
- 在通过叉积比较任何点之前,比较它们的 y 坐标以检查它们是否都位于 x 轴的另一侧。如果我们这样做,那么 y 轴上方的点肯定有一个更小的极角。如果它们位于同一侧,则使用叉积进行比较。该技术可确保任意两点之间的极角差始终保持小于 π。
角落案例
- 当两点共线时会出现极端情况。如果两个点都位于 y 轴的另一侧,则与任何其他点相比,第一象限中的点将具有较小的 0 o极角。
- 注意:认为靠近参考点 P 0的点更小,即使它们具有相同的极角。
算法:以下方法使用归并排序算法从参考点P 0开始按照极角升序对N个点进行排序。
- 使用归并排序,通过简单地比较它们的向量相对于参考点 P 0的方向,按极角对所有点进行排序。
- 如果任何点 P A相对于参考点 P O与 P B处于逆时针方向,则 P A相对于 P B具有更大的极角。
- 在我们的排序算法中使用归并排序过程。
插图:
Consider 4 points [(-1, 0), (0, 1), (1, 0), (0, -1)] and reference point O(0, 0)
The merge sort procedure of the algorithm is quite straight forward.
The comparison and hence the core step of the algorithm lies in the merge procedure.
merge (-1, 0) and (0, 1)
- (-1, 0) and (0, 1) lie on same side of y-axis and their cross product = -1 is not 0.
- Hence they are not collinear.
- Simply compare them using their direction.
- Since cross product is negative, (-1, 0) lies in anti-clockwise direction with respect to (0, 1) and hence has a greater polar angle.
merge (1, 0) and (0, -1)
- (1, 0) and (0, -1) lie on opposite side of x-axis. Hence, (1, 0) has a smaller polar angle since it lies above x-axis.
merge [(0, 1), (-1, 0)] and [(1, 0), (0, -1)]
- The algorithm first compares (0, 1) with (1, 0). (0, 1) and (1, 0) lie on same side of y-axis and their cross product = -1 is not 0.
- Hence they are not collinear.
- Simply compare them using their direction.
- Since cross product is negative, (0, 1) lies in anti-clockwise direction with respect to (1, 0) and hence has a greater polar angle. Our final array so far is [(1, 0)].
- The algorithm then compares (0, 1) with (0, -1). (0, 1) and (0, -1) lie on opposite side of x-axis. Hence, (0, 1) has a smaller polar angle since it lies above x-axis. Our final array so far is [(1, 0), (0, 1)].
- The algorithm then compares (-1, 0) with (0, -1). (-1, 0) and (0, -1) lie on opposite side of x-axis. Hence, (-1, 0) has a smaller polar angle since it lies above x-axis. Our final array so far is [(1, 0), (0, 1), (-1, 0)].
- Then, fill the remaining point (0, -1) in the array. The final array is [(1, 0), (0, 1), (-1, 0), (0, -1)].
下面是上述方法的实现:
Java
// Java code for the above approach:
import java.util.Arrays;
public class SortingPoints {
// Encapsulates a 2D point
public static class Point {
int x;
int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
@Override
public String toString()
{
return "(" + x + ", " + y + ")";
}
}
// Checks if directed line segment pi-pk
// is clockwise or anti-clockwise to pi-pj
private static int direction(
Point pi, Point pj, Point pk)
{
return (pk.x - pi.x) * (pj.y - pi.y)
- (pj.x - pi.x) * (pk.y - pi.y);
}
// Compares polar angle of two
// points pk and pj with respect to pi.
// In case all three points are
// collinear, i.e. vector pi-pk
// is collinear to vector pi-pj,
// then it compares their
// distance from the origin
private static int compare(
Point pi, Point pj, Point pk)
{
// if two points lie on opposite side
// of x-axis, then simply return above
// point as it has smaller polar angle
if (pk.y - pi.y >= 0 && pj.y - pi.y < 0)
return 1;
if (pk.y - pi.y < 0 && pj.y - pi.y >= 0)
return -1;
// Check if both vectors are collinear
if (direction(pi, pj, pk) == 0) {
// Check if the vector lies on the y-axis
if (pk.x - pi.x == 0 && pj.x - pi.x == 0) {
// If vector lie on the y-axis
// Then return -1, If vector pk-pi
// has smaller magnitude
// In comparison to pj-pi
// Since vector with smaller
// Magnitude has its end-point
// Closer to the origin point p0
if (Math.abs(pk.y - pi.y)
> Math.abs(pj.y - pi.y))
return -1;
else
return 1;
}
// If vectors do not lie on the y-axis,
// Then, either vector lie on the
// x-axis or lie in the Same line.
// Check if vectors lie on x-axis
// and are on opposite side of y-axis
// In such a case, return the vector
// which lies on the positive x-axis
// since it is in the first quadrant
// and closer to origin
if (pk.x - pi.x > 0
&& pj.x - pi.x < 0)
return 1;
else if (pk.x - pi.x < 0
&& pj.x - pi.x > 0)
return -1;
// In other cases, return the point
// closer to the reference point
else if (Math.abs(pk.x - pi.x)
> Math.abs(pj.x - pi.x))
return -1;
else
return 1;
}
// If vectors lie on same side
// of y-axis and are not collinear,
// Then compare them using Cross product
if (direction(pi, pj, pk) > 0)
return 1;
else
return -1;
}
// Merge two sorted subarray in
// Sorted order in place in
// The points array range of
// First subarray: p-q range
// Of second subarray: q + 1 - r
private static void merge(
Point[] points,
int p, int q,
int r, Point p0)
{
int n1 = q - p + 1;
int n2 = r - q;
Point[] L = new Point[n1];
Point[] R = new Point[n2];
for (int i = 0; i < n1; i++)
L[i] = points[p + i];
for (int j = 0; j < n2; j++)
R[j] = points[q + 1 + j];
int i = 0, j = 0;
for (int k = p; k <= r; k++) {
if (i == n1)
points[k] = R[j++];
else if (j == n2)
points[k] = L[i++];
else if (compare(p0, L[i], R[j]) < 0)
points[k] = L[i++];
else
points[k] = R[j++];
}
}
// Arranges the point in ascending
// according to their polar angle
public static void mergeSort(
Point[] points, int p,
int r, Point p0)
{
int q;
if (p < r) {
q = (p + r) / 2;
mergeSort(points, p, q, p0);
mergeSort(points, q + 1, r, p0);
merge(points, p, q, r, p0);
}
}
// Driver code
public static void main(String[] args)
{
// EXAMPLE 1
Point O = new Point(0, 0);
Point[] points
= { new Point(1, 0),
new Point(0, -1),
new Point(-1, 0),
new Point(0, 1) };
System.out.println(
"Points: "
+ Arrays.toString(points));
System.out.println();
mergeSort(points, 0, 3, O);
System.out.println(
"After sorting with"
+ " reference point " + O);
System.out.println(
Arrays.toString(points));
System.out.println();
// EXAMPLE 2
O = new Point(1, 1);
mergeSort(points, 0, 3, O);
System.out.println(
"After sorting with"
+ " reference point " + O);
System.out.println(
Arrays.toString(points));
System.out.println();
// EXAMPLE 3
points = new Point[] {
new Point(-98, -65),
new Point(-65, -60),
new Point(65, -52),
new Point(-10, -47),
new Point(-7, -46),
new Point(26, -89),
new Point(72, -26),
new Point(66, -52),
new Point(69, 73),
new Point(-64, 0)
};
O = new Point(0, 0);
System.out.println(
"Points: "
+ Arrays.toString(points));
System.out.println();
mergeSort(points, 0, 9, O);
System.out.println(
"After sorting with"
+ " reference point " + O);
System.out.println(
Arrays.toString(points));
}
}
Points: [(1, 0), (0, -1), (-1, 0), (0, 1)]
After sorting with reference point (0, 0)
[(1, 0), (0, 1), (-1, 0), (0, -1)]
After sorting with reference point (1, 1)
[(0, 1), (-1, 0), (0, -1), (1, 0)]
Points: [(-98, -65), (-65, -60), (65, -52), (-10, -47), (-7, -46), (26, -89), (72, -26), (66, -52), (69, 73), (-64, 0)]
After sorting with reference point (0, 0)
[(69, 73), (-64, 0), (-98, -65), (-65, -60), (-10, -47), (-7, -46), (26, -89), (65, -52), (66, -52), (72, -26)]
时间复杂度: O(N log N)
辅助空间: O(1)