항해99, 4주차 리스트 정렬
Today I learned
2022/01/31
회고록
1/31
항해 99, 알고리즘 3주차
교재 : 파이썬 알고리즘 인터뷰 / 이것이 코딩테스트다(동빈좌)
병합/퀵정렬(Sort)
1. 이론
퀵정렬
퀵 정렬(quick sort) 알고리즘의 구체적인 개념
하나의 리스트를 피벗(pivot)을 기준으로 두 개의 비균등한 크기로 분할하고 분할된 부분 리스트를 정렬한 다음, 두 개의 정렬된 부분 리스트를 합하여 전체가 정렬된 리스트가 되게 하는 방법이다.
퀵 정렬은 다음의 단계들로 이루어진다.
분할(Divide): 입력 배열을 피벗을 기준으로 비균등하게 2개의 부분 배열(피벗을 중심으로 왼쪽: 피벗보다 작은 요소들, 오른쪽: 피벗보다 큰 요소들)로 분할한다.
정복(Conquer): 부분 배열을 정렬한다. 부분 배열의 크기가 충분히 작지 않으면 순환 호출 을 이용하여 다시 분할 정복 방법을 적용한다.
결합(Combine): 정렬된 부분 배열들을 하나의 배열에 합병한다.
순환 호출이 한번 진행될 때마다 최소한 하나의 원소(피벗)는 최종적으로 위치가 정해지므로, 이 알고리즘은 반드시 끝난다는 것을 보장할 수 있다.
병합정렬
합병 정렬(merge sort) 알고리즘의 구체적인 개념
하나의 리스트를 두 개의 균등한 크기로 분할하고 분할된 부분 리스트를 정렬한 다음, 두 개의 정렬된 부분 리스트를 합하여 전체가 정렬된 리스트가 되게 하는 방법이다.
합병 정렬은 다음의 단계들로 이루어진다.
분할(Divide): 입력 배열을 같은 크기의 2개의 부분 배열로 분할한다.
정복(Conquer): 부분 배열을 정렬한다. 부분 배열의 크기가 충분히 작지 않으면 순환 호출 을 이용하여 다시 분할 정복 방법을 적용한다.
결합(Combine): 정렬된 부분 배열들을 하나의 배열에 합병한다.
합병 정렬의 과정
추가적인 리스트가 필요하다.
각 부분 배열을 정렬할 때도 합병 정렬을 순환적으로 호출하여 적용한다.
합병 정렬에서 실제로 정렬이 이루어지는 시점은 2개의 리스트를 합병(merge)하는 단계 이다.
2. 문제
문제 설명
Given the head of a linked list, return the list after sorting it in ascending order.
Example 1:
Input: head = [4,2,1,3]
Output: [1,2,3,4]
https://leetcode.com/problems/sort-list/
3. MySol
- 3가지 방식의 Solution이 있다.
먼저 퀵소트 방식
class ListNode:
def __init__(self, val=None, next=None):
self.val = val
self.next = next
def partition(head, tail):
# if head.next is None:
# return head
pivot = tail.val
iNode = ListNode()
iNode.next = head
cur = head
while cur is not tail:
if cur.val <= pivot:
iNode = iNode.next
iNode.val, cur.val = cur.val, iNode.val
cur = cur.next
iNode.next.val, tail.val = tail.val, iNode.next.val
return iNode
def nodeQuickSort(head, tail, lv):
# if head is None or head.next is None or head is tail:
# return head
prevPivot = partition(head, tail)
if prevPivot.val is None:
return head
if head is prevPivot:
return head
nodeQuickSort(head, prevPivot, lv+1)
if prevPivot.next.next is tail or prevPivot.next is tail:
return head
nodeQuickSort(prevPivot.next.next, tail, lv+1)
return head
if __name__ == '__main__':
dummy = [4, 2, 1, 5, 3]
for i in reversed(range(len(dummy))):
if i + 1 != len(dummy):
node = ListNode(dummy[i], node)
else:
node = ListNode(dummy[i], None)
head = node
# 꼬리노드 구하기
tail = head
while tail.next is not None:
tail = tail.next
result = nodeQuickSort(head, tail, 0)
while True:
if result.next is not None:
print(f'{result.val}->', end="")
result = result.next
else:
print(f'{result.val}', end="")
break
머지소트방식
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def mergeNode(l1, l2):
#l1이 더 크면 swap 다시 말해, 앞의 노드가 더 크면
if l1 and l2:
if l1.val > l2.val:
l1, l2 = l2, l1
#l1, l2가 연속된 단일노드가 아닌 경우
l1.next = mergeNode(l1.next, l2)
#l1 = Null, l2 = node 일 경우 l2 반환
#l1 = node, l2 = node 일 경우 l1 반환
#l1 = node, l2 = Null 일 경우 l1 반환
return l1 or l2
def mergeSort(head):
#head가 None이거나 head.next가 None인 경우 return head - 다시 말해 정렬할 것이 없다.
if not (head and head.next):
return head
#mid node를 구한다
#런너 기법 활용, slow는 한칸씩 나아가고, fast는 두칸씩 나아가서, fast가 tail에 도달할 때까지
slow = head
fast = head
mid = None
# head가 tail을 넘어서기 전 까지 while 반복
while fast and fast.next:
mid = slow
slow = slow.next
fast = fast.next.next
mid.next = None
l1 = mergeSort(head)
l2 = mergeSort(slow)
result = mergeNode(l1, l2)
return result
if __name__ == '__main__':
dummy = [4, 2, 1, 3]
for i in reversed(range(len(dummy))):
if i + 1 != len(dummy):
node = ListNode(dummy[i], node)
else:
node = ListNode(i, None)
head = node
for _ in range(len(dummy)-1):
node = node.next
result = mergeSort(head)
print(f'result: {result}')
노드 -> 리스트 -> 노드 변환 방식
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def nodeToList(head):
p = head
lst = []
#값만 리스트로 변경
while p:
lst.append(p.val)
p = p.next
#정렬
lst.sort()
p = head
#노드에 정렬된 리스트 값 삽입
for i in range(len(lst)):
p.val = lst[i]
p = p.next
return head
#끗
if __name__ == '__main__':
dummy = [4, 2, 1, 3]
for i in reversed(range(len(dummy))):
if i + 1 != len(dummy):
node = ListNode(dummy[i], node)
else:
node = ListNode(i, None)
head = node
for _ in range(len(dummy)-1):
node = node.next
result = nodeToList(head)
print(f'result: {result}')
4. 배운 점
- 노드를 이용하여 퀵소트 및 머지소트를 구현하며 각 정렬에 대한 이해도를 높였다.
- 퀵정렬은 해당 노드들을 정렬시, 최악시 O(n^2) 효율의 안정성이 좋지 못하여 좋은 알고리즘이 못된다.
- 병합정렬이 차라리 안정적 O(nlogn)이다.
- 하지만, 실제로 이런 문제가 만약 코테에 나온다면 무조건 값만 추출한 리스트로 변환하여 정렬 후 다시 집어넣는 방법을 사용하는게 낫다.
5. 총평
퀵소트, 머지소트 구현 및 이해
Author And Source
이 문제에 관하여(항해99, 4주차 리스트 정렬), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jsw4215/항해99-4주차-리스트-정렬저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)