問題:平面點集求其中距離最近的兩個點及其距離。
思路:採用分治法,將“求n個點之間最小距離”問題劃分爲很多個“求n/t個點之間最小距離”問題。
(1)將lstPoint根據X座標由小到大排序得到點集pointsSortedX,方法很多,冒泡、選擇、插入、歸併,快排等,本文采用快排,其優點就不多說了。
(2)pointsSortedX爲一個點集,可以採用二分法分爲兩個數量均分的點集pointsX1([0,indexMid]),pointsX2([indexMid,Count-1]),對於每個分點集,求其中距離最近的兩個點及其距離,方法如前,採用遞歸求解;
(3)對於2個分點集,可得兩個最小距離,取其小者,爲分點集各自內的最小距離dis,但是仍然存在這種情況,即可能存在兩個點分別位於兩個分點集中,所以還需考慮2個分點集間的情況。而參考已求得的dis,2個分點集間區域可根據X座標取
X∈[pointsSortedX[indexMid].X - dis,pointsSortedX[indexMid].X + dis]範圍的點並構成臨時點集pointsTemp;
(4)將點集pointsTemp根據Y座標進行由小到大排序,對於每個點ptCurrent,求Y座標∈[ptCurrent.Y - dis, ptCurrent.Y + dis]的點ptCurrent的距離,並與dis取最小賦給dis;
圖解如下:
圖1 根據X座標排序
圖2 二分法分解
圖3 遞歸求解並考慮區域間點距離最小情況
圖4 代碼運行效果
public class ShortestDisPointFinder
{
public delegate bool CompareDelegate(HPoint2d point0, HPoint2d point1);
public class ShortestDisPointResult
{
public ShortestDisPointResult(double dis, HPoint2d point0, HPoint2d point1)
{
Distance = dis;
Point0 = point0;
Point1 = point1;
}
public double Distance { get; set; }
public HPoint2d Point0 { get; set; }
public HPoint2d Point1 { get; set; }
}
private List<HPoint2d> m_lstPoint;
public ShortestDisPointFinder(List<HPoint2d> lstPoint)
{
m_lstPoint = new List<HPoint2d>(lstPoint);
}
public ShortestDisPointResult GetShortestDistance()
{
// 首先按X座標排序
QuikSort(m_lstPoint, 0, m_lstPoint.Count - 1, CompareX);
// 分治法求最短距離
return Binarysearch(m_lstPoint, 0, m_lstPoint.Count - 1);
}
private ShortestDisPointResult Binarysearch(List<HPoint2d> lstPoint, int indexS, int indexE)
{
if (indexS + 1 == indexE)
return new
ShortestDisPointResult(lstPoint[indexE].Distance(lstPoint[indexS]), lstPoint[indexS], lstPoint[indexE]);
else if (indexS + 2 == indexE)
{
double dis1 = lstPoint[indexE].Distance(lstPoint[indexS]);
double dis2 = lstPoint[indexS + 1].Distance(lstPoint[indexS]);
double dis3 = lstPoint[indexS + 1].Distance(lstPoint[indexE]);
var result = new ShortestDisPointResult(dis1, lstPoint[indexS], lstPoint[indexE]);
if (result.Distance > dis2)
{
result.Distance = dis2;
result.Point0 = lstPoint[indexS];
result.Point1 = lstPoint[indexS + 1];
}
if (result.Distance > dis3)
{
result.Distance = dis3;
result.Point0 = lstPoint[indexS + 1];
result.Point1 = lstPoint[indexE];
}
return result;
}
ShortestDisPointResult resultRe = null;
int indexM = (indexE + indexS) / 2;
var result1 = Binarysearch(lstPoint, indexS, indexM);
var result2 = Binarysearch(lstPoint, indexM, indexE);
if (result1.Distance < result2.Distance)
resultRe = result1;
else
resultRe = result2;
// 判斷兩區域之間X座標在[indexM].X - dis到[indexM].X + dis內是否存在距離比dis小的兩個點
// 找出X座標在[indexM].X - dis到[indexM].X + dis內的點
var lstPtTemp = new List<HPoint2d>();
for(int index = indexS; index <= indexE; index++)
{
if (Math.Abs(lstPoint[index].X - lstPoint[indexM].X) < resultRe.Distance)
lstPtTemp.Add(lstPoint[index]);
}
// 將點集按Y座標排序
QuikSort(lstPtTemp, 0, lstPtTemp.Count - 1, CompareY);
// 對於點集中的任一點,其它點若在該點的[Y - dis, Y + dis]內即判斷與該點距離
for (int i = 0; i < lstPtTemp.Count - 1; i++)
{
for (int j = i + 1; j < lstPtTemp.Count; j++)
{
if (lstPtTemp[j].Y - lstPtTemp[i].Y < resultRe.Distance)
{
var disTemp = lstPtTemp[i].Distance(lstPtTemp[j]);
if (resultRe.Distance > disTemp)
{
resultRe.Distance = disTemp;
resultRe.Point0 = lstPtTemp[i];
resultRe.Point1 = lstPtTemp[j];
}
}
else
break;
}
}
return resultRe;
}
private bool CompareX(HPoint2d pointL, HPoint2d pointR)
{
if (pointL.X <= pointR.X)
return true;
return false;
}
private bool CompareY(HPoint2d pointL, HPoint2d pointR)
{
if (pointL.Y <= pointR.Y)
return true;
return false;
}
public void QuikSort(List<HPoint2d> lstPoint, int indexS, int indexE, CompareDelegate compare)
{
if (indexE <= indexS)
return;
int indexP = Partition(lstPoint, indexS, indexE, compare);
QuikSort(lstPoint, indexS, indexP - 1, compare);
QuikSort(lstPoint, indexP + 1, indexE, compare);
}
public int Partition(List<HPoint2d> lstPoint, int indexS, int indexE, CompareDelegate compare)
{
HPoint2d ptBase = lstPoint[indexE];
int i = indexS - 1;
for(int j = indexS; j < indexE; j++)
{
if(compare(lstPoint[j], ptBase))
{
i++;
var ptTemp = lstPoint[i];
lstPoint[i] = lstPoint[j];
lstPoint[j] = ptTemp;
}
}
var ptTemp2 = lstPoint[i + 1];
lstPoint[i + 1] = lstPoint[indexE];
lstPoint[indexE] = ptTemp2;
return i + 1;
}
}