Jost Do It.

그냥 IT해.

Study/알고리즘

프로그래머가 알아야 할 알고리즘 40 Chapter 3 알고리즘에 사용되는 자료구조

그냥하Jo. 2022. 10. 31. 07:15
반응형

3장. 알고리즘에 사용되는 자료구조

 

요약

정렬과 검색에 사용되는 기본 알고리즘들을 학습할 수 있었다.

 

 

내용 정리

 

3.1 정렬 알고리즘 이해하기

 

버블 정렬

 

버블 정렬(bubble sort): 리스트 안에서 가장 큰 값을 반복적으로 옮기는 알고리즘

  • 장점: 가장 간편하다
  • 단점: 속도가 매우 느린 알고리즘
  • 최선의 경우 시간 복잡도: O(n^2)
  • 최악의 경우 시간 복잡도: O(n^2)

 

버블 정렬의 작동 원리

  • 패스(pass)라는 과정을 반복한다.
    • 패스(pass): 리스트의 큰 값을 오른쪽으로 보내는 행위
    • 패스로 붙어있는 이웃끼리의 값을 비교한 후 오른쪽으로 보내게 된다. k번째 패스마다 리스트의 N-k+1번째 값(k번째로 큰 값)이 결정된다.
    • k번째 패스에서 리스트 내 패스 대상은 0번째 ~ N-k+1번째 까지의 원소들이다.

 

버블 정렬 파이썬 스니펫

def BubbleSort(list):
	# 리스트에 담긴 데이터를 순서대로 정렬
    lastElementIndex = len(list) - 1
    
    for passNo in range(lastElementIndex, 0, -1):
    	
        for idx in range(passNo):
        	if list[idx] > list[idx+1]:
            	list[idx], list[idx+1] = list[idx+1], list[idx]
    
    return list
    
list = [25, 21, 22, 24, 23, 27, 26]

print(list)
>> [25, 21, 22, 24, 23, 27, 26]

BubbleSort(list)
>> [21, 22, 23, 24, 25, 26, 27]
  • 두 개의 루프로 구성됨
    • 외부 루프: 패스를 의미
    • 내부 루프: 패스 내에서 가장 높은 값을 오른쪽으로 이동시킴

 

삽입 정렬

 

삽입 정렬(insertion sort): 자료 구조에서 데이터 포인트를 한칸씩 이동하며, 해당 값을 올바른 위치에 집어넣는 과정을 반복

  • 장점: 최선의 경우, 시간복잡도가 매우 작다.
  • 단점: 최악의 경우, 시간복잡도가 크다.
  • 최선의 경우 시간 복잡도: O(n)
  • 최악의 경우 시간 복잡도: O(n^2)
  • 크기가 작은 배열에 사용하는게 적절함

 

삽입 정렬 파이썬 스니펫

def InsertionSort(list):
	
    for i in range(1, len(list)):
    	j = i - 1
        element_next = list[i]
        
        while (list[j] > element_next) and (j >= 0):
            list[j+1] = list[j]
            j = j - 1
            
        list[j+1] = element_next
    
    return list
  • 메인 루프는 리스트 전체를 순회
  • 서브 루프는 해당 데이터 포인트에 위치한 값이 어디에 위치하는 것이 적절한지 결정

 

병합 정렬

 

병합 정렬(merge sort): 분리(spliting) + 병합(merging) 단계를 통해 데이터를 정렬하는 알고리즘

  • 장점: 최악의 경우 시간복잡도가 좋은편이다.
  • 단점: 속도에 입력 데이터의 정렬 여부가 영향을 미치지 않는다.
  • 최선의 경우 시간 복잡도: O(nlogn)
  • 최악의 경우 시간 복잡도: O(nlogn)

 

병합 정렬의 작동 원리

(1) 입력된 리스트를 크기가 같은 두 부분으로 분할한다.

(2) 나뉜 부분의 크기가 1이 될 때까지 반복해서 분리한다.

(3) 각 부분들에서 작은 값 순서대로 가져오면서 리스트를 병합해나간다.

(4) 마지막까지 병합해서 최종적으로 정렬된 리스트를 반환한다.

 

 

병합 정렬 파이썬 스니펫

def MergeSort(list):
	
    if len(list) > 1:
    	
        # 리스트를 분할한다.
    	mid = len(list) // 2
        left = list[:mid]
        right = list[mid:]
        
        # 나뉜 부분의 크기가 1이 될 때까지 재귀적으로 반복한다.
        MergeSort(left)
        MergeSort(right)
        
    	a = 0
    	b = 0
    	c = 0
    
    	while a < len(left) and b < len(right):
    	
            if left[a] < right[b]:
                list[c] = left[a]
                a += 1

            else:
                list[c] = right[b]
                b += 1
                c += 1
        
    	while a < len(left):
    		list[c] = left[a]
        	a += 1
        	c += 1
    
    	while b < len(right):
    		list[c] = right[b]
        	b += 1
        	c += 1
    
    return list

 

셸 정렬

 

셸 정렬(Shell sort): 삽입 정렬과 유사하나 인접한 이웃 대신 고정된 거리만큼 떨어진 데이터 포인트끼리 묶어서 정렬하는 방식

  • 장점
    • 정렬 시, 삽입 정렬에 비해 요소를 점프하는 거리가 크기 때문에 더 빠르게 정렬할 수 있다.
    • 리스트가 어느정도 정렬된 상태일 때 성능이 좋다.
  • 최선의 시간 복잡도: O(nlogn)
  • 최악의 시간 복잡도: O(n^2)
  • 중간 규모의 데이터셋에 사용하기 적절하다.

 

셸 정렬 작동 원리

  • 각 패스마다 고정된 거리에 떨어진 데이터 포인트를 하위 리스트로 묶어서 정렬하게 됨
  • 정렬에는 삽입 정렬이 사용됨
  • 패스가 진행될수록 거리를 줄이기 때문에 하위 리스트에 들어가는 데이터 수가 많아지게 됨 (하위 리스트의 수는 적어짐).

 

셸 정렬 파이썬 스니펫

def ShellSort(list):
    distance = len(list) // 2
    
    while distance > 0:
    	
        for i in range(distance, len(list)):
            temp = list[i]
            j = i
            
            # 하위 리스트 안의 요소들을 정렬
            while j >= distance and list[j - distance] > temp:
            	
                list[j] = list[j - distance]
                j = j - distance
                
            list[j] = temp
            
        # 다음 패스 시 거리를 절반으로 줄임
        distance //= 2
    
    return list

 

선택 정렬

 

선택 정렬(selection sort): 버블 정렬에서 필요한 교환 횟수를 최소화한 알고리즘

  • 장점: 버블 정렬보다 적은 교환으로 평균 성능이 올라감
  • 단점: 여전히 시간복잡도는 큼
  • 최악의 경우 시간 복잡도: O(n^2)
  • 큰 데이터셋에 사용하는 것은 피해야 함

 

선택 정렬의 작동 원리

  • 각 패스마다 가장 큰 값을 찾아내 오른쪽으로 바로 이동시킴
  • k번째 패스마다 k번째 큰 요소를 오른쪽 k번째에 위치시킴

 

선택 정렬 파이썬 스니펫

def SelectionSort(list):
	
    for fill_slot in range(len(list), -1, 0, -1):
    	
        max_index = 0
        
        for location in range(1, fill_slot + 1):
        
        	if list[location] > list[max_index]:
                    max_index = location
        
        list[fill_slot], list[max_index] = list[max_index], list[fill_slot]
        
    return list

 

 

정렬 알고리즘 선택

  • 입력하는 데이터의 크기와 상태에 따라 적절한 정렬 알고리즘이 달라진다.
    • 정렬됐거나 작은 리스트에 병합 정렬을 사용하는 것은 성능상 이점이 없을 수 있고, 오히려 코드를 복잡하게 만들 수 있다.
    • 규모가 큰 데이터에서는 병합 정렬이 좋은 성능을 보인다.

 

 

 

3.2 검색 알고리즘 이해하기

 

선형 검색

 

선형 검색(linear search): 데이터를 하나씩 살펴보는 것

  • 장점: 다른 검색 알고리즘과 달리 데이터 정렬을 요구하지 않음
  • 단점: 느린 탐색 속도
  • 최악의 경우 시간 복잡도: O(n)

 

선형 검색 파이썬 스니펫

def LinearSearch(list, item):
    index = 0
    found = False
    
    
    # 개별 데이터가 조건에 부합하는지 확인
    while index < len(list) and found is False:
    	
        if list[index] == item:
        	found = True
            
        else:
        	index += 1
            
    return found
    
list = [12, 33, 11, 99, 22, 55, 90]

print(LinearSearch(list, 12))
>> True

print(print(LinearSearch(list, 91))
>> False

 

이진 검색

 

이진 검색(binary search): 반복적으로 검색 대상을 반으로 줄이면서 데이터를 찾아가는 방식

  • 데이터가 정렬돼 있어야 한다.
  • 최저와 최고 인덱스를 갱신하는 방식으로 값을 찾아나간다.
  • 최악의 경우 시간 복잡도: O(logn)

 

이진 검색 파이썬 스니펫

def BinarySearch(list, item):
    first = 0
    last = len(list) - 1
    found = False
    
    
    while first <= last and not found:
    	midpoint = (first + last) // 2
        
        if list[midpoint] == item:
        	found = True
            
        else:
            if item < list[midpoint]:
            	last = midpoint - 1
            
            else:
            	first = midpoint + 1

    return found

 

 

보간 검색

 

보간 검색(interpolation search): 조금 더 정교하게 검색할 대상의 위치를 추정하는 방식

  • 데이터를 미리 정렬해야 한다.
  • 조건에 부합한 데이터가 있을 가능성이 높은 지점을 중간 지점으로 선택한다.
  • 장점: 데이터 분포가 고를 경우, 탐색 속도가 빠를 수 있다.
  • 단점: 데이터 분포가 극단적일 경우, 탐색 속도가 많이 느려질 수 있다.
  • 최선의 경우 시간 복잡도: O(log(log(n)))
  • 최악의 경우 시간 복잡도: O(n)

 

보간 검색 파이썬 스니펫

def IntPolSearch(list, x):
    idx0 = 0
    idxn = (len(list) - 1
    found = False
    
    while idx0 <= idxn and x >= list[idx0] and x <= list[idxn]:
    
    	# 중간 지점을 설정
        mid = idx0 + int(
        	((foloat(idxn - idx0) / (list[idxn] - list[idx0])) * (x - list[idx0]))
            )
        
        # 검색 대상과 중간 지점의 값 비교
        if list[mid] == x:
            found = True
            return found
            
       	if list[mid] < x:
            idx0 = mid + 1
    
    return found

 

3.3 활용사례 - 이민 관리청에 접수된 서류 조회하기

  • 현실 세계의 문제 성격과 데이터 유형, 양에 따라서 적절한 알고리즘이 다를 수 있다.
  • 문제를 잘 파악하고 적절한 알고리즘을 선택하는 것이 중요하다.

 

 

참고

 

학습 Point!

  • 버블 정렬에서 한번 순회되는 과정을 pass라고 한다.
  • 셸 정렬의 경우 gap의 크기에 따라 최선의 상황에서 시간복잡도가 달라질 수 있다.
    • 책의 방법대로 gap의 크기를 전체 길이 절반으로 줄이면서 정렬하는 경우 최선의 시간복잡도는 O(nlogn)에 가깝게 되고, gap 탐색이 줄수록 최선의 시간복잡도는 O(n)에 가까워진다.
반응형