-
[Python 자료구조 #정렬과 탐색1] 선택정렬, 삽입정렬, 버블정렬, 정렬 응용한 집합Programming 기초/Python 2023. 5. 23. 21:32
* 정렬(sorting)
- 오름차순( ascending order)
- 내림차순(descending order)
- 레코드(record) : 정렬시켜야 될 대상
- 필드(field) 레코드는 여러 개의 필드로 이루어지고, 필드는 속성들이다.
- 키(key) 또는 정렬 키(sort key) : 정렬의 기준이 되는 필드
- 정렬 : 레코드들을 키(key)의 순서로 재배열하는 것
* 정렬 장소에 따른 분류
- 내부(internal)정렬 : 모든 데이터가 메인 메모리에 올라와 있는 정렬
- 외부(external)정렬 : 외부 기억 장치에 대부분의 데이터가 있고 일부만 메모리에 올려 정렬하는 방법으로 대용량 자료를 정렬하기 위해 사용한다.
* 구현 복잡도와 알고리즘 효율성에 따른 분류
- 단순하지만 비효율적인 방법 : 삽입정렬, 선택정렬, 버블 정렬 등
- 복잡하지만 효율적인 방법 : 퀵 정렬, 힙 정렬, 병합 정렬, 기수 정렬 등
* 안정성에 따른 분류
- 안정성(stability)이란 입력 데이터에 동일한 킷값을 갖는 레코드가 여러 개 존재할 경우, 정렬 후에도 이들의 상대적인 위치가 바뀌지 않는 것. 안정성을 충족하는 정렬에는 삽입정렬, 버블정렬, 병합정렬 등이 있다.
* 선택 정렬(selection sort)
- 리스트에서 가장 작은 숫자를 선택해서 앞쪽으로 옮기는 방법. 오른쪽 리스트에서 가장 작은 숫자를 선택하여 왼쪽 리스트의 맨 뒤로 이동하는 작업
- 오른쪽 리스트 : 아직 정렬되지 않은 리스트
- 왼쪽 리스트 : 정렬 완료된 리스트
- 실제로 두 개의 배열을 사용하진 않는다. 정렬이 안 된 리스트에서 최솟값이 선택되면 이 값을 배열의 첫 번째 요소와 교환하면 되기 때문이다.
- 선택 정렬은 입력 배열 이외에 추가적인 배열을 사용하지 않는다. 이러한 정렬 방법을 제자리 정렬(in-place sorting)라고 한다.
- 시간 복잡도 O(n^2)이고 알고리즘이 간단하고, 입력 자료의 구성과 상관없이 자료 이동 횟수가 결정된다는 장점있음.
def selection_sort(A): n = len(A) # 리스트 n의 길이 for i in range(n - 1): # i = 0 ~ n-2 least = i # 최솟값의 인덱스 for j in range(i + 1, n): # j = i+1 ~ n-1 if A[j] < A[least]: least = j #최솟값의 인덱스를 찾음 A[i], A[least] = A[least], A[i] printStep(A, i + 1) def printStep(arr, val): print(" Step %2d = " % val, end="") print(arr)
data = [5, 3, 8, 4, 9, 1, 6, 2, 7] print("Original :", data) selection_sort(data) print("Selection :", data) ----------------------------------------------- Original : [5, 3, 8, 4, 9, 1, 6, 2, 7] Step 1 = [1, 3, 8, 4, 9, 5, 6, 2, 7] Step 2 = [1, 2, 8, 4, 9, 5, 6, 3, 7] Step 3 = [1, 2, 3, 4, 9, 5, 6, 8, 7] Step 4 = [1, 2, 3, 4, 9, 5, 6, 8, 7] Step 5 = [1, 2, 3, 4, 5, 9, 6, 8, 7] Step 6 = [1, 2, 3, 4, 5, 6, 9, 8, 7] Step 7 = [1, 2, 3, 4, 5, 6, 7, 8, 9] Step 8 = [1, 2, 3, 4, 5, 6, 7, 8, 9] Selection : [1, 2, 3, 4, 5, 6, 7, 8, 9]
* 삽입 정렬(insertion sort)
- 삽입 정렬의 복잡도는 입력 자료의 구성에 따라서 달라진다. 이미 정렬되어 있는 경우는 가장 빠르다 O(n)
- 최악의 경우는 입력 자료가 역으로 정렬된 경우이다.O(n^2)
- 대부분의 레코드가 이미 정렬되어 있는 경우 효율적으로 사용될 수 있다.
- 삽입된 카드로 인해 항목들이 뒤로 이동됨.
def insertion_sort(A): n = len(A) for i in range(1, n): # 위부 루프 i = 1 ~ n-1 key = A[i] # key는 삽입 대상 j = i - 1 while j >= 0 and A[j] > key: # 내부 루프 A[j + 1] = A[j] # 항목들을 뒤로 한 칸씩 이동 j -= 1 A[j + 1] = key # 항목 삽입 printStep(A, i) def printStep(arr, val): print(" Step %2d = " % val, end="") print(arr)
data = [5, 3, 8, 4, 9, 1, 6, 2, 7] print("Original :", data) insertion_sort(data) print("insertion :", data) --------------------------------------- Original : [5, 3, 8, 4, 9, 1, 6, 2, 7] Step 1 = [3, 5, 8, 4, 9, 1, 6, 2, 7] Step 2 = [3, 5, 8, 4, 9, 1, 6, 2, 7] Step 3 = [3, 4, 5, 8, 9, 1, 6, 2, 7] Step 4 = [3, 4, 5, 8, 9, 1, 6, 2, 7] Step 5 = [1, 3, 4, 5, 8, 9, 6, 2, 7] Step 6 = [1, 3, 4, 5, 6, 8, 9, 2, 7] Step 7 = [1, 2, 3, 4, 5, 6, 8, 9, 7] Step 8 = [1, 2, 3, 4, 5, 6, 7, 8, 9] insertion : [1, 2, 3, 4, 5, 6, 7, 8, 9]
* 버블정렬(bubble sort)
- 인접한 2개의 레코드를 비교하여 크기가 순서대로가 아니면 서로 교환하는 방법. 비교-교환 과정이 한번 완료되면( 한 번의 스캔) 가장 큰 레코드가 리스트의 오른쪽 끝으로 이동된다.
def bubble_sort(A): n = len(A) for i in range(n - 1, 0, -1): # 외부 루프 : n-1 ~ 1 bChanged = False for j in range(i): # 내부 루프 : 0 ~ i-1 if A[j] > A[j + 1]: # 순서가 맞지 않으면 A[j], A[j + 1] = A[j + 1], A[j] # 교환 bChanged = True # 교환이 발생했음 if not bChanged: # 교환이 없으면 종료 break printStep(A, n - i) # 중간 과정 출력용 문장 def printStep(arr, val): print(" Scan %2d 완료 = " % val, end="") print(arr)
data = [5, 3, 8, 4, 9, 1, 6, 2, 7] print("Original :", data) bubble_sort(data) print("insertion :", data) -------------------------------------------- Original : [5, 3, 8, 4, 9, 1, 6, 2, 7] Scan 1 완료 = [3, 5, 4, 8, 1, 6, 2, 7, 9] Scan 2 완료 = [3, 4, 5, 1, 6, 2, 7, 8, 9] Scan 3 완료 = [3, 4, 1, 5, 2, 6, 7, 8, 9] Scan 4 완료 = [3, 1, 4, 2, 5, 6, 7, 8, 9] Scan 5 완료 = [1, 3, 2, 4, 5, 6, 7, 8, 9] Scan 6 완료 = [1, 2, 3, 4, 5, 6, 7, 8, 9] insertion : [1, 2, 3, 4, 5, 6, 7, 8, 9]
* 정렬 응용 : 집합 다시보기
- 집합의 원소들이 정렬되어 있으면 집합의 비교나 합집합, 차집합, 교집합 등을 훨씬 효율적으로 구현할 수 있다. 그래서 정렬을 이용해서 집합의 원소들을 항상 정렬된 순으로 저장하려고 한다.
- 집합의 삽입연산 등의 ADT(추상 데이터 타입)을 변경해야한다.
더보기class Set:def __init__( self ):self.items = []
def size( self ):return len(self.items)def display(self, msg):print(msg, self.items)
# def contains(self, item) :# return item in self.items
def contains(self, item) :for i in range(len(self.items)):if self.items[i] == item :return Truereturn False
def insert(self, elem) :if elem not in self.items :self.items.append(elem)
def delete(self, elem) :if elem in self.items :self.items.remove(elem)
def union( self, setB ):setC = Set()setC.items = list(self.items)for elem in setB.items :if elem not in self.items :setC.items.append(elem)return setC
def intersect( self, setB ):setC = Set()for elem in setB.items :if elem in self.items :setC.items.append(elem)return setC
def difference( self, setB ):setC = Set()for elem in self.items:if elem not in setB.items:setC.items.append(elem)return setC* Set_insert : 삽입 연산
def insert(self, elem) : # 삽입연산 if elem in self.items : return # 이미 있는 경우 for idx in range(len(self.items)) : if elem < self.items[idx] : # 삽입할 위치 idx를 찾음 self.items.insert(idx, elem) return self.items.append(elem)
* Set_ _eq_ _() : 비교 연산
def __eq__( self, setB ): # 비교 연산 if self.size() != setB.size() : return False for idx in range(len(self.items)): if self.items[idx] != setB.items[idx] : return False return True
정렬되지 않은 상태에서 두 집합이 같은지 비교하려면 O(n^2) 알고리즘이 된다. 배열이 정렬되었다면 원소의 개수를 확인하고 순서대로 원소를 비교하면 된다. 정렬되어 있으면 시간 복잡도는 O(n)이 된다.
_ _eq_ _() 파이썬에서 미리 정의된 특별한 메소드로 ==연산자에 대한 연산자 중복 함수이다. Set 클래스에서 이 메소드를 구현하면 두 Set 객체 A와 B를 A==B와 같이 ==연산자를 이용해 비교할 수 있다. 산술 연산자(+,-,/,*)나 비교 연산자 등을 포함한 다양한 연산자 중복 메소드가 정의되어 있는데, 이들은 모두 _ _로 시작하여 _ _로 끝나는 이름을 갖는다.
* Set_insert : 삽입 연산
- 두 집합의 원소들이 크기순으로 정렬되어 있으므로, 가장 작은 원소들부터 비교하여 더 작은 원소를 새로운 집합에 넣고 그 집합의 인덱스를 증가시킨다.
- 만약 두 집합의 현재 원소가 같으면 하나만을 새 집합에 넣으면 된다. 인덱스는 모두 증가시킨다.
- 한쪽 집합이 모두 처리되면 나머지 집합의 남은 모든 원소를 순서대로 새 집합에 넣는다.
def union( self, setB ): newSet = Set() # 반환할 합집합 a = 0 # 집합 self의 원소에 대한 인덱스 b = 0 # 집합 setB의 원소에 대한 인덱스 while a < len( self.items ) and b < len( setB.items ) : valueA = self.items[a] # 집합 self의 현재 원소 valueB = setB.items[b] # 집합 setB의 현재 원소 if valueA < valueB : newSet.items.append( valueA ) a += 1 elif valueA > valueB : newSet.items.append( valueB ) b += 1 else : newSet.items.append( valueA ) a += 1 b += 1 while a < len( self.items ): newSet.items.append( self.items[a] ) a += 1 while b < len( setB.items) : newSet.items.append( setB.items[b] ) b += 1 return newSet
'Programming 기초 > Python' 카테고리의 다른 글
[Python 자료구조 #트리1] 이진 트리, 전위/중위/후위/레벨 순회 (1) 2023.05.25 [Python 자료구조 #정렬과 탐색2] 순차탐색, 이진탐색, 보간탐색, 해싱 (0) 2023.05.24 [Python 자료구조 #연결리스트3] 이중연결리스트 : 연결된 덱 (0) 2023.05.23 [Python 자료구조 #연결리스트2] 원형연결리스트 : 연결된 큐 (0) 2023.05.23 [Python 자료구조 #연결리스트1] 단순연결리스트 : 연결된 스택 (0) 2023.05.22