mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-11-27 15:01:08 +00:00
Add graham scan algorithm (#4205)
* Add graham scan algorithm * Fix argument name p with point * Add tests in inner function * updating DIRECTORY.md * Fix graham scan for isort --profile=black Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
This commit is contained in:
parent
0435128cc0
commit
a796ccf1ce
|
@ -30,6 +30,8 @@
|
|||
* [Binary Count Trailing Zeros](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_count_trailing_zeros.py)
|
||||
* [Binary Or Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_or_operator.py)
|
||||
* [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py)
|
||||
* [Count Number Of One Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_number_of_one_bits.py)
|
||||
* [Reverse Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/reverse_bits.py)
|
||||
* [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py)
|
||||
|
||||
## Blockchain
|
||||
|
@ -122,6 +124,7 @@
|
|||
* [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py)
|
||||
* [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py)
|
||||
* [Lowest Common Ancestor](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lowest_common_ancestor.py)
|
||||
* [Merge Two Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/merge_two_binary_trees.py)
|
||||
* [Non Recursive Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/non_recursive_segment_tree.py)
|
||||
* [Number Of Possible Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/number_of_possible_binary_trees.py)
|
||||
* [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py)
|
||||
|
@ -274,6 +277,7 @@
|
|||
|
||||
## Graphics
|
||||
* [Bezier Curve](https://github.com/TheAlgorithms/Python/blob/master/graphics/bezier_curve.py)
|
||||
* [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/graphics/koch_snowflake.py)
|
||||
* [Vector3 For 2D Rendering](https://github.com/TheAlgorithms/Python/blob/master/graphics/vector3_for_2d_rendering.py)
|
||||
|
||||
## Graphs
|
||||
|
@ -520,6 +524,7 @@
|
|||
* [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py)
|
||||
* [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py)
|
||||
* [Gauss Easter](https://github.com/TheAlgorithms/Python/blob/master/other/gauss_easter.py)
|
||||
* [Graham Scan](https://github.com/TheAlgorithms/Python/blob/master/other/graham_scan.py)
|
||||
* [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py)
|
||||
* [Integeration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/other/integeration_by_simpson_approx.py)
|
||||
* [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py)
|
||||
|
@ -840,6 +845,7 @@
|
|||
* [Merge Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_insertion_sort.py)
|
||||
* [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py)
|
||||
* [Natural Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/natural_sort.py)
|
||||
* [Odd Even Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_sort.py)
|
||||
* [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py)
|
||||
* [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py)
|
||||
* [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py)
|
||||
|
@ -856,6 +862,7 @@
|
|||
* [Recursive Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_quick_sort.py)
|
||||
* [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py)
|
||||
* [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py)
|
||||
* [Slowsort](https://github.com/TheAlgorithms/Python/blob/master/sorts/slowsort.py)
|
||||
* [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py)
|
||||
* [Strand Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/strand_sort.py)
|
||||
* [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py)
|
||||
|
|
171
other/graham_scan.py
Normal file
171
other/graham_scan.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
"""
|
||||
This is a pure Python implementation of the merge-insertion sort algorithm
|
||||
Source: https://en.wikipedia.org/wiki/Graham_scan
|
||||
|
||||
For doctests run following command:
|
||||
python3 -m doctest -v graham_scan.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import deque
|
||||
from enum import Enum
|
||||
from math import atan2, degrees
|
||||
from sys import maxsize
|
||||
|
||||
|
||||
def graham_scan(points: list[list[int, int]]) -> list[list[int, int]]:
|
||||
"""Pure implementation of graham scan algorithm in Python
|
||||
|
||||
:param points: The unique points on coordinates.
|
||||
:return: The points on convex hell.
|
||||
|
||||
Examples:
|
||||
>>> graham_scan([(9, 6), (3, 1), (0, 0), (5, 5), (5, 2), (7, 0), (3, 3), (1, 4)])
|
||||
[(0, 0), (7, 0), (9, 6), (5, 5), (1, 4)]
|
||||
|
||||
>>> graham_scan([(0, 0), (1, 0), (1, 1), (0, 1)])
|
||||
[(0, 0), (1, 0), (1, 1), (0, 1)]
|
||||
|
||||
>>> graham_scan([(0, 0), (1, 1), (2, 2), (3, 3), (-1, 2)])
|
||||
[(0, 0), (1, 1), (2, 2), (3, 3), (-1, 2)]
|
||||
|
||||
>>> graham_scan([(-100, 20), (99, 3), (1, 10000001), (5133186, -25), (-66, -4)])
|
||||
[(5133186, -25), (1, 10000001), (-100, 20), (-66, -4)]
|
||||
"""
|
||||
|
||||
if len(points) <= 2:
|
||||
# There is no convex hull
|
||||
raise ValueError("graham_scan: argument must contain more than 3 points.")
|
||||
if len(points) == 3:
|
||||
return points
|
||||
# find the lowest and the most left point
|
||||
minidx = 0
|
||||
miny, minx = maxsize, maxsize
|
||||
for i, point in enumerate(points):
|
||||
x = point[0]
|
||||
y = point[1]
|
||||
if y < miny:
|
||||
miny = y
|
||||
minx = x
|
||||
minidx = i
|
||||
if y == miny:
|
||||
if x < minx:
|
||||
minx = x
|
||||
minidx = i
|
||||
|
||||
# remove the lowest and the most left point from points for preparing for sort
|
||||
points.pop(minidx)
|
||||
|
||||
def angle_comparer(point: list[int, int], minx: int, miny: int) -> float:
|
||||
"""Return the angle toward to point from (minx, miny)
|
||||
|
||||
:param point: The target point
|
||||
minx: The starting point's x
|
||||
miny: The starting point's y
|
||||
:return: the angle
|
||||
|
||||
Examples:
|
||||
>>> angle_comparer([1,1], 0, 0)
|
||||
45.0
|
||||
|
||||
>>> angle_comparer([100,1], 10, 10)
|
||||
-5.710593137499642
|
||||
|
||||
>>> angle_comparer([5,5], 2, 3)
|
||||
33.690067525979785
|
||||
"""
|
||||
# sort the points accorgind to the angle from the lowest and the most left point
|
||||
x = point[0]
|
||||
y = point[1]
|
||||
angle = degrees(atan2(y - miny, x - minx))
|
||||
return angle
|
||||
|
||||
sorted_points = sorted(points, key=lambda point: angle_comparer(point, minx, miny))
|
||||
# This insert actually costs complexity,
|
||||
# and you should insteadly add (minx, miny) into stack later.
|
||||
# I'm using insert just for easy understanding.
|
||||
sorted_points.insert(0, (minx, miny))
|
||||
|
||||
# traversal from the lowest and the most left point in anti-clockwise direction
|
||||
# if direction gets right, the previous point is not the convex hull.
|
||||
class Direction(Enum):
|
||||
left = 1
|
||||
straight = 2
|
||||
right = 3
|
||||
|
||||
def check_direction(
|
||||
starting: list[int, int], via: list[int, int], target: list[int, int]
|
||||
) -> Direction:
|
||||
"""Return the direction toward to the line from via to target from starting
|
||||
|
||||
:param starting: The starting point
|
||||
via: The via point
|
||||
target: The target point
|
||||
:return: the Direction
|
||||
|
||||
Examples:
|
||||
>>> check_direction([1,1], [2,2], [3,3])
|
||||
Direction.straight
|
||||
|
||||
>>> check_direction([60,1], [-50,199], [30,2])
|
||||
Direction.left
|
||||
|
||||
>>> check_direction([0,0], [5,5], [10,0])
|
||||
Direction.right
|
||||
"""
|
||||
x0, y0 = starting
|
||||
x1, y1 = via
|
||||
x2, y2 = target
|
||||
via_angle = degrees(atan2(y1 - y0, x1 - x0))
|
||||
if via_angle < 0:
|
||||
via_angle += 360
|
||||
target_angle = degrees(atan2(y2 - y0, x2 - x0))
|
||||
if target_angle < 0:
|
||||
target_angle += 360
|
||||
# t-
|
||||
# \ \
|
||||
# \ v
|
||||
# \|
|
||||
# s
|
||||
# via_angle is always lower than target_angle, if direction is left.
|
||||
# If they are same, it means they are on a same line of convex hull.
|
||||
if target_angle > via_angle:
|
||||
return Direction.left
|
||||
if target_angle == via_angle:
|
||||
return Direction.straight
|
||||
if target_angle < via_angle:
|
||||
return Direction.right
|
||||
|
||||
stack = deque()
|
||||
stack.append(sorted_points[0])
|
||||
stack.append(sorted_points[1])
|
||||
stack.append(sorted_points[2])
|
||||
# In any ways, the first 3 points line are towards left.
|
||||
# Because we sort them the angle from minx, miny.
|
||||
current_direction = Direction.left
|
||||
|
||||
for i in range(3, len(sorted_points)):
|
||||
while True:
|
||||
starting = stack[-2]
|
||||
via = stack[-1]
|
||||
target = sorted_points[i]
|
||||
next_direction = check_direction(starting, via, target)
|
||||
|
||||
if next_direction == Direction.left:
|
||||
current_direction = Direction.left
|
||||
break
|
||||
if next_direction == Direction.straight:
|
||||
if current_direction == Direction.left:
|
||||
# We keep current_direction as left.
|
||||
# Because if the straight line keeps as straight,
|
||||
# we want to know if this straight line is towards left.
|
||||
break
|
||||
elif current_direction == Direction.right:
|
||||
# If the straight line is towards right,
|
||||
# every previous points on those straigh line is not convex hull.
|
||||
stack.pop()
|
||||
if next_direction == Direction.right:
|
||||
stack.pop()
|
||||
stack.append(sorted_points[i])
|
||||
return list(stack)
|
Loading…
Reference in New Issue
Block a user