파이썬, Lower Bound 및 Upper Bound 구현
- CS/알고리즘
- 2021. 8. 22.
구현 전, 알아두어야 할 것
Lower Bound 및 Upper Bound는 모두 경계값을 찾는 함수다. 이 함수는 반드시 정렬이 된 값들에서만 사용할 수 있다.
Lower Bound 파이썬 코드 구현 : O(logn)
Lower Bound는 찾고자 하는 값이 가장 처음으로 나오는 위치를 찾는 함수다. 예를 들어, 4를 찾는다면 4가 가장 처음으로 나오는 위치를 찾는 함수다. 아래 표를 예시로 들 경우, Index는 6이 정답이 된다.
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
Value | 1 | 1 | 1 | 2 | 2 | 3 | 4 | 4 | 4 |
Lower Bound 함수를 구현할 때, 나는 다음과 같이 구현했다.
1. 이진 검색을 기준으로 값을 찾는다. (logn의 시간복잡도 구현)
2. 이진 검색 시, 다음과 같은 로직으로 접근한다.
2-1. 현재 Mid값이 찾고자 하는 값보다 작은 경우, Left = Mid + 1
2-2. 현재 Mid값이 찾고자 하는 값보다 큰 경우, Right = Mid - 1
2-3. 현재 Mid값이 찾고자 하는 값과 같을 경우, Right = Mid
(단, 2-3에서 무한 루프 방지를 위해 right와 mid가 같을 경우 Break를 걸어준다)
3. While문이 완료되면, 현재 Left, Right 값을 기준으로 Mid를 구한다. 그 후 Mid에 있는 값을 기준으로 판별한다.
3-1. Mid에 있는 값이 찾고자 하는 값이면, Mid를 Return 한다.
3-2. Mid에 있는 값이 찾고자 하는 값이 아니면, -1를 Return한다.
현재 Mid값이 찾고자 하는 값보다 작은 경우에는 이 부분부터 왼쪽 부분은 더 이상 살펴볼 필요가 없어진다. 또한 이 값보다 큰 경우 역시 살펴볼 필요가 없기 때문에 오른쪽 부분은 더 이상 살펴볼 필요가 없어진다. 따라서 각각에 대해 Left = Mid + 1 , Right = Mid -1 을 적용하게 되었다.
우리가 계속 꾸준히 살펴봐야하는 부분은 Mid가 찾고자 하는 값일 때이다. 이건 처음으로 그 수가 나오는 것을 찾는 것이기 때문에, 지금 살펴본 Mid가 처음일 수도 있다. 따라서 이 부분을 빼고 탐색을 하는 것은 안되기 때문에 이 섹터의 값은 항상 저장해두어야 한다.
단, 이 값이 처음으로 나오는 인덱스를 살피는 것이기 때문에 논리상 Right = Mid를 적용하는 것이 맞다. Right = Mid를 적용해두면, 여러 개의 똑같은 값이 나올 때 Right의 값이 점차 왼쪽으로 접근한다. 즉, 이 값이 나오는 가장 처음의 위치를 알 수 있다. 이걸 살짝 응용하면 Left = Mid를 하게 될 경우, Left의 값이 점차 오른쪽으로 접근 하기 때문에 이 값이 나오는 가장 마지막 위치를 알 수 있다.
파이썬으로 짠 코드는 다음과 같다.
def lower_bound(left, right, k) :
while left <= right :
mid = (left + right) // 2
if my_list[mid] < k :
left = mid + 1
elif my_list[mid] > k :
right = mid - 1
elif my_list[mid] == k :
if right == mid :
break
right = mid
if my_list[mid] == k :
return mid
else :
return -1
위의 과정을 하나하나 도식화해서 그려보면 아래와 같아 진다.
마지막에 L,R,M이 두번 나오게 되는데, 내가 짠 코드는 이 때 루프가 중단된다. 왜냐하면 시작했을 때의 Mid와 Right가 같아지는 지점이 되기 때문이다.
Upper Bound 파이썬 코드 구현 : O(logn)
Upper Bound는 찾고자 하는 값보다 큰 값이 가장 처음으로 나오는 위치를 찾는 함수다. 예를 들어, 2를 찾는다면 2보다 큰 값이 가장 처음으로 나오는 위치를 찾는 함수다. 아래의 표에서는 Value 3이 답을 만족하기 때문에 Index 5가 Return 된다.
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
Value | 1 | 1 | 1 | 2 | 2 | 3 | 4 | 4 | 4 |
Upper Bound 함수를 구현할 때, 나는 다음과 같이 구현했다.
1. 이진 검색을 기준으로 값을 찾는다. (logn의 시간복잡도 구현)
2. 이진 검색 시, 다음과 같은 로직으로 접근한다. (While문을 다 돌 때까지 기다린다)
2-1. 현재 Mid값이 찾고자 하는 값보다 작거나 같은 경우, Left = Mid + 1
2-2. 현재 Mid값이 찾고자 하는 값보다 큰 경우, Right = Mid
3. While문이 완료되면, 현재 Left, Right 값을 기준으로 Mid를 구한다. 그 후 Mid에 있는 값을 기준으로 판별한다.
3-1. Mid에 있는 값이 찾고자 하는 값이면, Mid를 Return 한다.
3-2. Mid에 있는 값이 찾고자 하는 값이 아니면, -1를 Return한다.
Mid 값이 찾고자 하는 값보다 작거나 같은 경우는 Left의 값을 Mid + 1 로 주게 되는데, 이 이유는 찾고자 하는 값보다 작거나 같은 경우는 우리가 고려하는 경우가 아니기 때문에 더 이상 보지 않아도 되기 때문이다. 반대로 Mid 값이 찾고 하는 값보다 큰 경우에는 우리가 반드시 고려해야하는 부분이기 때문에 Right = Mid로 하여 앞으로 계속 고려해주는 것이다.
파이썬으로 짠 코드는 다음과 같다.
def upper_bound(left, right, k) :
while left < right :
mid = (left + right) // 2
if my_list[mid] <= k :
left = mid + 1
elif my_list[mid] > k :
right = mid
mid = (left + right) // 2
if my_list[mid] > k :
print(mid)
else :
print(-1)
위의 과정을 하나하나 도식화해서 그려보면 아래와 같아진다.
'CS > 알고리즘' 카테고리의 다른 글
누적합 계산하기 (0) | 2021.10.04 |
---|---|
그리디 알고리즘 (0) | 2021.09.27 |
파이썬 알고리즘 필요 라이브러리 (0) | 2021.09.12 |
파이썬 리스트 정렬 (0) | 2021.08.15 |
다익스트라, 플로이드-와샬 알고리즘 (0) | 2021.08.03 |