Skip to content

Commit 9b7ecd6

Browse files
authored
Closest Pair of points algorithm
1 parent 898db6f commit 9b7ecd6

File tree

9 files changed

+331
-0
lines changed

9 files changed

+331
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
//Created by Ahmed Nader (github: AhmedNader42) on 4/4/18.
2+
3+
func ClosestPairOf(points: [Point]) -> (minimum:Double, firstPoint:Point, secondPoint:Point) {
4+
var innerPoints = mergeSort(points, sortAccording : true)
5+
let result = ClosestPair(&innerPoints, innerPoints.count)
6+
return (result.minValue, result.firstPoint, result.secondPoint)
7+
}
8+
9+
func ClosestPair(_ p : inout [Point],_ n : Int) -> (minValue: Double,firstPoint: Point,secondPoint: Point)
10+
{
11+
// Brute force if only 3 points (To end recursion)
12+
if n <= 3
13+
{
14+
var i=0, j = i+1
15+
var minDist = Double.infinity
16+
var newFirst:Point? = nil
17+
var newSecond:Point? = nil
18+
while i<n
19+
{
20+
j = i+1
21+
while j < n
22+
{
23+
if dist(p[i], p[j]) <= minDist
24+
{
25+
minDist = dist(p[i], p[j])
26+
newFirst = p[i]
27+
newSecond = p[j]
28+
}
29+
j+=1
30+
}
31+
i+=1
32+
33+
}
34+
return (minDist, newFirst ?? Point(0,0), newSecond ?? Point(0,0))
35+
}
36+
37+
38+
39+
let mid:Int = n/2
40+
let line:Double = (p[mid].x + p[mid+1].x)/2
41+
42+
// Split the array.
43+
var leftSide = [Point]()
44+
var rightSide = [Point]()
45+
for s in 0..<mid
46+
{
47+
leftSide.append(p[s])
48+
}
49+
for s in mid..<p.count
50+
{
51+
rightSide.append(p[s])
52+
}
53+
54+
55+
// Recurse on the left and right part of the array.
56+
let valueFromLeft = ClosestPair(&leftSide, mid)
57+
let minLeft:Double = valueFromLeft.minValue
58+
let valueFromRight = ClosestPair(&rightSide, n-mid)
59+
let minRight:Double = valueFromRight.minValue
60+
61+
// Starting current min must be the largest possible to not affect the real calculations.
62+
var min = Double.infinity
63+
64+
var first:Point
65+
var second:Point
66+
67+
// Get the minimum between the left and the right.
68+
if minLeft < minRight {
69+
min = minLeft
70+
first = valueFromLeft.firstPoint
71+
second = valueFromLeft.secondPoint
72+
}
73+
else {
74+
min = minRight
75+
first = valueFromRight.firstPoint
76+
second = valueFromRight.secondPoint
77+
}
78+
79+
// Sort the array according to Y.
80+
p = mergeSort(p, sortAccording: false)
81+
82+
83+
var strip = [Point]()
84+
85+
// If the value is less than the min distance away in X from the line then take it into consideration.
86+
var i=0, j = 0
87+
while i<n
88+
{
89+
if abs(p[i].x - line) < min
90+
{
91+
strip.append(p[i])
92+
j+=1
93+
}
94+
i+=1
95+
}
96+
97+
98+
i=0
99+
var x = i+1
100+
var temp = min
101+
var tempFirst:Point = Point(0,0)
102+
var tempSecond:Point = Point(0,0)
103+
// Get the values between the points in the strip but only if it is less min dist in Y.
104+
while i<j
105+
{
106+
x = i+1
107+
while x < j
108+
{
109+
if (abs(strip[x].y - strip[i].y)) > min { break }
110+
if dist(strip[i], strip[x]) < temp
111+
{
112+
temp = dist(strip[i], strip[x])
113+
tempFirst = strip[i]
114+
tempSecond = strip[x]
115+
}
116+
x+=1
117+
}
118+
i+=1
119+
}
120+
121+
if temp < min
122+
{
123+
min = temp;
124+
first = tempFirst
125+
second = tempSecond
126+
}
127+
return (min, first, second)
128+
}
129+
130+
131+
132+
133+
// MergeSort the array (Taken from Swift Algorithms Club with
134+
// minor addition)
135+
// sortAccodrding : true -> x, false -> y.
136+
func mergeSort(_ array: [Point], sortAccording : Bool) -> [Point] {
137+
guard array.count > 1 else { return array }
138+
let middleIndex = array.count / 2
139+
let leftArray = mergeSort(Array(array[0..<middleIndex]), sortAccording: sortAccording)
140+
let rightArray = mergeSort(Array(array[middleIndex..<array.count]), sortAccording: sortAccording)
141+
return merge(leftPile: leftArray, rightPile: rightArray, sortAccording: sortAccording)
142+
}
143+
144+
145+
private func merge(leftPile: [Point], rightPile: [Point], sortAccording: Bool) -> [Point] {
146+
147+
var compare : (Point, Point) -> Bool
148+
149+
// Choose to compare with X or Y.
150+
if sortAccording
151+
{
152+
compare = { p1,p2 in
153+
return p1.x < p2.x
154+
}
155+
}
156+
else
157+
{
158+
compare = { p1, p2 in
159+
return p1.y < p2.y
160+
}
161+
}
162+
163+
var leftIndex = 0
164+
var rightIndex = 0
165+
var orderedPile = [Point]()
166+
if orderedPile.capacity < leftPile.count + rightPile.count {
167+
orderedPile.reserveCapacity(leftPile.count + rightPile.count)
168+
}
169+
170+
while true {
171+
guard leftIndex < leftPile.endIndex else {
172+
orderedPile.append(contentsOf: rightPile[rightIndex..<rightPile.endIndex])
173+
break
174+
}
175+
guard rightIndex < rightPile.endIndex else {
176+
orderedPile.append(contentsOf: leftPile[leftIndex..<leftPile.endIndex])
177+
break
178+
}
179+
180+
if compare(leftPile[leftIndex], rightPile[rightIndex]) {
181+
orderedPile.append(leftPile[leftIndex])
182+
leftIndex += 1
183+
} else {
184+
orderedPile.append(rightPile[rightIndex])
185+
rightIndex += 1
186+
}
187+
}
188+
return orderedPile
189+
}
190+
191+
192+
// Structure to represent a point.
193+
struct Point
194+
{
195+
var x: Double
196+
var y: Double
197+
198+
init(_ x:Double,_ y:Double) {
199+
self.x = x
200+
self.y = y
201+
}
202+
}
203+
// Get the distance between two points a, b.
204+
func dist(_ a: Point,_ b: Point) -> Double
205+
{
206+
let equation:Double = (((a.x-b.x)*(a.x-b.x))) + (((a.y-b.y)*(a.y-b.y)))
207+
return equation.squareRoot()
208+
}
209+
210+
211+
var a = Point(0,2)
212+
var b = Point(6,67)
213+
var c = Point(43,71)
214+
var d = Point(1000,1000)
215+
var e = Point(39,107)
216+
var f = Point(2000,2000)
217+
var g = Point(3000,3000)
218+
var h = Point(4000,4000)
219+
220+
221+
var points = [a,b,c,d,e,f,g,h]
222+
let endResult = ClosestPairOf(points: points)
223+
print("Minimum Distance : \(endResult.minimum), The two points : (\(endResult.firstPoint.x ),\(endResult.firstPoint.y)), (\(endResult.secondPoint.x),\(endResult.secondPoint.y))")
224+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='macos' executeOnSourceChanges='false'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>

Closest Pair/ClosestPair.playground/playground.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>
Loading

Closest Pair/Images/Case.png

17.2 KB
Loading

Closest Pair/Images/Strip.png

18.4 KB
Loading

Closest Pair/README.markdown

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# ClosestPair
2+
3+
Closest Pair is an algorithm that finds the closest pair of a given array of points By utilizing the Divide and Conquer methodology of solving problems so that it reaches the correct solution with O(nlogn) complexity.
4+
5+
![Given points and we're required to find the two red ones](../Closest Pair/Images/1200px-Closest_pair_of_points.png)
6+
7+
As we see in the above image there are an array of points and we need to find the closest two, But how do we do that without having to compare each two points which results in a whopping O(n^2) complexity?
8+
9+
Here is the main algorithm (Steps) we'll follow.
10+
11+
- Sort the array according to their position on the X-axis so that they are sorted in the array as they are naturally in math.
12+
13+
```swift
14+
var innerPoints = mergeSort(points, sortAccording : true)
15+
```
16+
17+
- Divide the points into two arrays Left, Right and keep dividing until you reach to only having 3 points in your array.
18+
19+
- The base case is you have less than 3 points compare those against each other (Brute force) then return the minimum distance you found and the two points.
20+
21+
- Now we get the first observation in the below image, There could be 2 points both very close to each other and indeed those two are the closest pair but since our algorithm so far divides from the middle
22+
23+
```swift
24+
let line:Double = (p[mid].x + p[mid+1].x)/2
25+
```
26+
27+
and just recursively calls itself until it reaches the base case we don't detect those points.
28+
29+
![ Points lying near the division line](../Closest Pair/Images/Case.png)
30+
31+
- To solve this we start by sorting the array on the Y-axis to get the points in their natural order and then we start getting the difference between the X position of the point and the line we drew to divide and if it is less than the min we got so far from the recursion we add it to the strip
32+
33+
```swift
34+
var strip = [Point]()
35+
var i=0, j = 0
36+
while i<n
37+
{
38+
if abs(p[i].x - line) < min
39+
{
40+
strip.append(p[i])
41+
j+=1
42+
}
43+
i+=1
44+
}
45+
```
46+
47+
- After you insert the points that could possibly give you a better min distance we get to another observation in the image below.
48+
49+
![The strip with 4 points shown](..//Closest Pair/Images/Strip.png)
50+
51+
- Searching the strip is a brute force loop (But doesn't that just destroy everything we did? You ask) but it has an advantage it could never iterate on more than 8 points (worst case).
52+
53+
- The reason is that the strip is constructed as a rectangle with sides of length = min that we got from the recursion and we ignore any points that have a Y difference bigger than min distance so to be able to place them ALL inside the rectangle with these conditions they'll have to be in the shape above with each one of them EXACTLY min distance away from the other which gives us 4 possible points for each one and 8 in total.
54+
55+
```swift
56+
while i<j
57+
{
58+
x = i+1
59+
while x < j
60+
{
61+
if (abs(strip[x].y - strip[i].y)) > min { break }
62+
if dist(strip[i], strip[x]) < temp
63+
{
64+
temp = dist(strip[i], strip[x])
65+
tempFirst = strip[i]
66+
tempSecond = strip[x]
67+
}
68+
x+=1
69+
}
70+
i+=1
71+
}
72+
```
73+
74+
- Of course not every time you end up with the same shape but this is the worst case and it's rare to happen so in reality you end up with far less points valid for comparison and this is why the algorithm gets performance in addition to the sorting tricks we did.
75+
76+
- Compare the points in the strip and if you find a smaller distance replace the current one with it.
77+
78+
79+
So this is the rundown of how the algorithm works and you could see the fun little math tricks used to optimize this and we end up with O(nlogn) complexity mainly because of the sorting.
80+
81+
82+
## See also
83+
84+
See the playground to play around with the implementation of the algorithm
85+
86+
[Wikipedia](https://en.wikipedia.org/wiki/Closest_pair_of_points_problem)
87+
88+
*Written for Swift Algorithm Club by [Ahmed Nader](https://github.com/ahmednader42)*

0 commit comments

Comments
 (0)