[Python]Searching & Sorting(검색&정렬 관련 알고리즘)
Search(검색)
1. LinearSearch(선형 검색) - O(N)
- 가장 기본
def linearSearch(theValues, target):
n = len(theValues)
for i in range(n):
if theValues[i] == target:
return True
return False
- 정렬된 리스트에서 검색은 조기종료가 가능 (장점) - 성능이 좀 더 좋겠죠
def sortedLinearSearch(theValues, item):
n = len(theValues)
for i in range(n):
if theValues[i] == item:
return True
elif theValues[i] > item: # 조기종료
return False
return False
2. BinarySearch(바이너리 검색) - O(N), O(logN)
- 개선 전 : O(N), 개선 후 O(logN)
def binarySearch(theValues, target):
low = 0
high = len(theValues) -1 # 맨끝칸은 n-1
# for x in range(len(theValues)): # 이렇게 하면, 복잡도가 O(N)이 됨.
while low <= high: # O(logN)
mid = (high+low) // 2 # 정수 부분만 남겨줌 ('//'의 역할)
if theValues[mid] == target:
return True
elif target < theValues[mid]:
high = mid -1
else:
low = mid +1
return False
Sort(정렬)
1. Bubble Sort(버블 정렬) - O(N2)
- for j in range(i+1,n-1):
- for j in range(n-1-i): # for문 둘다 가능
- 현재 위치(j) 다음 위치(j+1) 비교를 반복해서 현재 위치값이 크면 교환을 반복한다.
- 참고 : range(n-1)하는 이유는 list index out of range에러 안뜨게끔.
import random
def bubbleSort(theSeq):
n = len(theSeq) # 리스트 길이
for i in range(n-1) : # 크기가 n인 리스트이면 (n-1)번 반복
# for j in range(i+1,n-1): # 이렇게 for문 돌려도 가능
for j in range(n-1-i): # i=0일때 (n-1 - 0)번 버블, i=1일때 (n-1 - 1)번 버블, i=2일때 (n - 1 - 2)번 버블, ..., i=(n-2)일때 1번 버블
# 현재 위치의 값과 (현재 위치+1)인 위치의 값을 비교하고 현재 위치의 값이 크면 교환
if theSeq[j] > theSeq[j+1] :
tmp = theSeq[j]
theSeq[j] = theSeq[j+1]
theSeq[j+1] = tmp
return theSeq
N = 10 # 10개의 난수 생성
unorderedList = [random.randint(1, 1000) for x in range(N)] # 중복을 포함한 N개의 난수 생성 리스트
print(unorderedList)
orderedList = bubbleSort(unorderedList)
print(orderedList)
2. Selection Sort(선택 정렬) - O(N2)
- 값을 선택해서 제일 작은값을 Linear로 쭉 찾고나서 마지막에 바꿔주고 continue한다.(반복)
- 사람과 유사하게 작동
def selectionSort(theSeq):
n = len(theSeq)
for i in range(n-1):
smallNdx = i
for j in range(i+1, n):
if theSeq[j] < theSeq[smallNdx]: # SELECTION
smallNdx = j
if smallNdx != i:
tmp = theSeq[i]
theSeq[i] = theSeq[smallNdx]
theSeq[smallNdx] = tmp
return theSeq
3. Insertion Sort(삽입 정렬) - O(N2)
- 자신의 위치를 찾아서 삽입하는 정렬 테크닉
def insertionSort(theSeq):
n = len(theSeq)
for i in range(1, n):
value = theSeq[i]
pos = i
while pos > 0 and value < theSeq[pos -1]: # SHIFTING
theSeq[pos] = theSeq[pos - 1]
pos -= 1
theSeq[pos] = value # INSERTION
return theSeq
4. Merging Sorted Lists(정렬된 리스트 합병 정렬)
- 정렬된 2개의 리스트를 매개변수로 받아, 새로운 정렬된 리스트를 생성
방법1) 일단 다 추가하고 Sorting 실행 - O(NlogN)
def MergeSortedLists(listA, listB) -> [int]:
newList = list()
# 합병
for a in listA:
newList.append(listA[a])
for b in listB:
newList.append(listB[b])
# 정렬 메소드 이용
newList.sort() # O(NlogN) Python의 sort라이브러리는 병합정렬(NlogN)으로 구성된다.
return newList # 반환
방법2) 맨 위의 값을 비교해서 차례로 넣고 남는 값들 채워 넣기 - O(N)
- 조건 : listA, listB는 정렬된 리스트여야 함.
def MergeSortedLists(listA, listB):
newList = list()
a = 0
b = 0
while a < len(listA) and b < len(listB):
if listA[a] < listB[b]:
newList.append(listA[a])
a += 1
else :
newList.append(listB[b])
b += 1
while a < len(listA):
newList.append(listA[a])
a += 1
while b < len(listB):
newList.append(listB[b])
b +=1
return newList
5. Merge Sort(합병 정렬)
- 위의 4번은 정렬된 리스트를 받았을때의 합병정렬을 의미한거지 혼동X
Basic Merge sort in Python
- 재귀적인 호출(매우 비효율), slice사용으로 새로운 list가 각각 생성(매우 비효율), merge는 동일. 애초에 array(즉, 고정된 배열)은 slice를 제공하지도 않는다.
- 아마도?? O(N2)일거라고 추측. 아니여도 O(nlogn)보단 훨씬 안좋을것이다.
def pythonMergeSort(theList):
if len(theList) <= 1: # Base Case
return theList
else: # Recursive Case
mid = len(theList) //2
leftHalf = pythonMergeSort(theList[:mid])
rightHalf = pythonMergeSort(theList[mid:])
newList = MergeSortedLists(leftHalf, rightHalf)
return newList
fire = [2,4,1,53,5,4,63,63,45,346,35,342,3]
print(fire) # [2,4,1,53,5,4,63,63,45,346,35,342,3]
print(pythonMergeSort(fire)) # [1, 2, 3, 4, 4, 5, 35, 45, 53, 63, 63, 342, 346]
향상된 Merge sort - O(NlogN)
- Split virtually : 리스트를 물리적으로 나누지 말고 가상적으로 나누자(인덱스 마커들 사용)
- 전체 데이터를 1/2씩 접근할 때 logN에 데이터 N개 => O(NlogN)
# 향상 Merge sort
def mergeVirtualSeq(theSeq, left, right, end, tmpArray):
a = left
b = right
m = 0
while a < right and b < end:
if theSeq[a] < theSeq[b]:
tmpArray[m] = theSeq[a]
a+=1
else:
tmpArray[m] = theSeq[b]
b+=1
m+=1
while a < right:
tmpArray[m] = theSeq[a]
a+=1
m+=1
while b < end:
tmpArray[m] = theSeq[b]
b+=1
m+=1
for i in range(end - left): # 마지막 원래 시퀸스에 넣어주기
theSeq[i+left] = tmpArray[i]
def recMergeSort(theSeq, first, last, tmpArray):
if first == last:
return
else:
mid = (first + last) // 2
recMergeSort(theSeq, first, mid, tmpArray)
recMergeSort(theSeq, mid+1, last, tmpArray)
mergeVirtualSeq(theSeq, first, mid+1, last+1, tmpArray)
def mergeSort(theSeq):
n = len(theSeq)
# tmpArray = Array(n)
tmpArray = [0 for i in range(n)]
recMergeSort(theSeq, 0, n-1, tmpArray)
fire = [2,4,1,53,5,4,63,63,45,346,35,342,3]
print(fire) # [2,4,1,53,5,4,63,63,45,346,35,342,3]
mergeSort(fire)
print(fire) # [1, 2, 3, 4, 4, 5, 35, 45, 53, 63, 63, 342, 346]
6. Quick Sort(퀵 정렬) - O(N2)
- 대부분은 O(NlogN)이긴 하지만, 최악은 O(N2)이다.
- C++의 경우 sort라이브러리가 퀵 정렬로 구성되어있는데, 복잡도가 O(NlogN)으로 나타난다. 즉, 대부분은 O(NlogN)이며, O(N2)이 안되게끔 C++에서는 수정해놨다.
- pivot으로 구성되어있다, mergeSort와 다르게 추가적인 임시 저장공간이 필요없다는 장점.
# 퀵 정렬
def quickSort(theSeq):
n = len(theSeq)
recQuickSort(theSeq, 0, n-1)
def recQuickSort(theSeq, first, last):
if first >= last:
return
else:
pos = partitionSeq(theSeq, first, last)
recQuickSort(theSeq, first, pos-1)
recQuickSort(theSeq, pos+1, last)
def partitionSeq(theSeq, first, last):
pivot = theSeq[first]
left = first +1
right = last
while left < right: # 둘이 만날때 까지 수행
while left < right and theSeq[left] < pivot:
left += 1
while right >= left and theSeq[right] >= pivot:
right -= 1
if left < right : # 만약 파티션 종료 안되었다면 Swap
tmp = theSeq[left]
theSeq[left] = theSeq[right]
theSeq[right] = tmp
if right != first:
theSeq[first] = theSeq[right]
theSeq[right] = pivot
return right
fire = [2,4,1,53,5,4,63,63,45,346,35,342,3]
quickSort(fire)
print(fire) # [1, 2, 3, 4, 4, 5, 35, 45, 53, 63, 342, 63, 346]
7. Radix Sort(기수 정렬) - O(nw) = O(N)
- 빠른 분배 정렬 알고리즘. Bin sort 혹은 Bucket sort라고도 함.
- 자리수 마다 추출 공식 : digit = (key // column) % 10
- 큐 생성 : O(k), D번만큼 분배와 모음을 진행 : O(nd) => O(k+nd) = O(N) k : 큐의 개수, n : 키의 개수, d : 키의 길이 (위에서 w는 키의 길이를 의미)
def radixSort(intList, numDigits):
binArray = [None]*10
for k in range(10): # bin을 10개 미리 만들어둠 (숫자 : 0~9니까)
binArray[k] = Queue()
column = 1 # 일의자리 먼저 key!!
for d in range(numDigits):
for key in intList:
digit = (key// column) % 10
binArray[digit].put(key)
i = 0
for bin in binArray: # binArray전부 intList로 넣기
# (1의자리 bin들, 10의자리 bin들, 100의자리 bin들이 순서대로 intList를 계속 바꾸고 채워나가 줌. 마지막 자리의 bin들이 적용됬을땐 intList 정렬은 끝)
while bin.qsize() != 0:
intList[i] = bin.get()
i += 1
column *= 10 # 숫자 자리수 변경 (1이면 10의 자리로)
intList = [212, 413, 125, 531, 522, 411, 634, 632, 457, 346, 358, 342, 390]
radixSort(intList, 3) # 3은 자리수를 의미
print(intList) # [125, 212, 342, 346, 358, 390, 411, 413, 457, 522, 531, 632, 634]
8. Heap Sort(힙 정렬) - O(NlogN)
- Heaps : 완전 이진 트리, 보통 리스트로 구현
Simple HeapSort
def simpleHeapSort(theSeq):
# Create an array-based max-heap.
n = len(theSeq)
heap = MaxHeap(n) # MaxHeap 자료구조를 활용 - 크기 n으로 초기화
# Build a max-heap from the list of values.
for item in theSeq: # 정렬되지 않은 데이터를 힙에 넣음
heap.add(item)
# Extract each value from the heap and store them back into the list.
for i in range(n, 0, -1): # 오름차순
theSeq[i] = heap.extract() # 힙으로부터 데이터를 추출하면서 정렬
향상된 힙 정렬
- 사실 뭐가 향상된건진 잘 모르겠음.
def heapsort(theSeq):
n = len(theSeq)
# Build a max-heap within the same array.
for i in range(n):
siftUp(theSeq, i)
# Extract each value and rebuild the heap.
for j in range(n-1, 0, -1):
tmp = theSeq[j]
theSeq[j] = theSeq[0]
theSeq[0] = tmp
siftDown(theSeq, j-1, 0)
댓글남기기