Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

Manhattan Distance

Data Structures : 정렬, 탐색 본문

Computer Science/Data Structures

Data Structures : 정렬, 탐색

timotheekim10 2023. 2. 13. 02:29

정렬이란?

  • 가장 기본적이고 중요한 알고리즘
  • 비교할 수 있는 모든 속성들은 정렬의 기준이 될 수 있음
  • 정렬된 자료는 탐색에 더 효율적

 

정렬 알고리즘 종류

  • 단순하지만 비효율적인 방법
    • 선택 정렬, 삽입 정렬, 버블 정렬 등
  • 복잡하지만 효율적인 방법
    • 퀵 정렬, 힙 정렬, 병합 정렬, 기수 정렬 등

 

선택 정렬

  • 리스트에서 가장 작은 숫자를 선택하여 리스트 앞쪽으로 옮기는 방법
  • 시간 복잡도: O(n²)
def selection_sort(A):  # 선택 정렬
    n = len(A)
    for i in range(n-1):
        least = i
        for j in range(i+1, n):
            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 {} = ".format(val), end='')
    print(arr)


data = [5, 3, 8, 4, 9, 1, 6, 2, 7]
print("Original:", data)
# Original: [5, 3, 8, 4, 9, 1, 6, 2, 7]
selection_sort(data)
# 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]
print("Selection:", data)
# Selection: [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

삽입 정렬

  • 정렬되어 있는 부분에 새로운 레코드를 올바른 위치에 삽입하는 과정을 반복하는 방법
  • 시간 복잡도
    • 최선: O(n)
    • 최악: O(n²)
    • 평균: O(n²)
def insertion_sort(A):  # 삽입 정렬
    n = len(A)
    for i in range(1, n):
        key = A[i]
        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 {} = ".format(val), end='')
    print(arr)


data = [5, 3, 8, 4, 9, 1, 6, 2, 7]
print("Original:", data)
# Original: [5, 3, 8, 4, 9, 1, 6, 2, 7]
insertion_sort(data)
# 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]
print("Selection:", data)
# Selection: [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

버블 정렬

  • 인접한 2개의 레코드를 비교하여 순서대로가 아니면 서로 교환
  • 비교-교환 과정을 리스트의 전체에 수행
  • 끝으로 이동한 레코드를 제외하고 다시 스캔 반복
  • 시간 복잡도: O(n²)
def bubble_sort(A):  # 버블 정렬
    n = len(A)
    for i in range(n-1, 0, -1):
        bChanged = False
        for j in range(i):
            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("Step {} = ".format(val), end='')
    print(arr)


data = [5, 3, 8, 4, 9, 1, 6, 2, 7]
print("Original:", data)
# Original: [5, 3, 8, 4, 9, 1, 6, 2, 7]
bubble_sort(data)
# Step 1 = [3, 5, 4, 8, 1, 6, 2, 7, 9]
# Step 2 = [3, 4, 5, 1, 6, 2, 7, 8, 9]
# Step 3 = [3, 4, 1, 5, 2, 6, 7, 8, 9]
# Step 4 = [3, 1, 4, 2, 5, 6, 7, 8, 9]
# Step 5 = [1, 3, 2, 4, 5, 6, 7, 8, 9]
# Step 6 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print("Selection:", data)
# Selection: [1, 2, 3, 4, 5, 6, 7, 8, 9]

 

탐색이란?

  • 테이블에서 원하는 탐색키를 가진 레코드를 찾는 작업
    • 탐색을 위한 자료구조
    • 키를 가진 레코드(엔트리)의 집합
  • 엔트리
    • 키: 레코드를 구분할 수 있는 탐색키
    • 값: 탐색키와 관련된 값

 

맵 ADT

  • 데이터: 키를 가진 엔트리의 집합
  • 연산
    • search(key): 탐색키 key를 가진 레코드를 찾아 반환
    • insert(entry): 주어진 entry를 맵에 삽입
    • delete(key): 탐색키 key를 가진 레코드를 찾아 삭제

 

탐색 알고리즘 종류

  • 간단한 탐색 알고리즘
    • 순차 탐색, 이진 탐색
  • 고급 탐색 알고리즘
    • 해싱

 

순차 탐색

  • 정렬되지 않은 배열에도 적용 가능
  • 처음부터 마지막까지 하나씩 검사
  • 시간 복잡도: O(n)
class Entry:
    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __str__(self):
        return str("{}:{}".format(self.key, self.value))


def sequential_search(A, key, low, high):  # 순차 탐색
    for i in range(low, high+1):
        if A[i].key == key:
            return i
    return None

 

이진 탐색

  • 정렬된 배열에 적용 가능
  • 찾고자 하는 항목이 배열의 중앙에 있는 값을 기준으로 왼쪽 또는 오른쪽 부분 배열에 있는지를 알아내어 탐색의 범위를 반으로 줄어가며 탐색
  • 시간 복잡도: O(log n)
class Entry:
    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __str__(self):
        return str("{}:{}".format(self.key, self.value))


def binary_search(A, key, low, high):  # 이진 탐색
    if low <= high:
        middle = (low+high) // 2
        if key == A[middle].key:
            return middle
        elif key < A[middle].key:
            return binary_search(A, key, low, middle-1)
        else:
            return binary_search(A, key, middle+1, high)
    return None

 

리스트를 이용한 순차 탐색 맵

class Entry:
    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __str__(self):
        return str("{}:{}".format(self.key, self.value))


class SequentialMap:  # 순차탐색 맵
    def __init__(self):
        self.table = []  # 맵의 레코드 테이블

    def size(self):  # 맵의 크기 반환
        return len(self.table)

    def display(self, msg):  # 맵의 전체 레코드 출력
        print(msg)
        for entry in self.table:
            print(" ", entry)

    def insert(self, key, value):  # 삽입 연산
        self.table.append(Entry(key, value))

    def search(self, key):  # 순차탐색 연산
        pos = sequential_search(self.table, key, 0, self.size()-1)
        if pos is not None:
            return self.table[pos]
        else:
            return None

    def delete(self, key):  # 삭제 연산
        for i in range(self.size()):
            if self.table[i].key == key:
                self.table.pop(i)
                return

 

해싱이란?

  • 키 값에 대한 산술적 연산에 의해 테이블의 주소를 계산
  • 해시 함수
    • 키 값에 의해 데이터가 저장된 위치를 계산하는 함수
  • 해시 테이블
    • 해시 함수에 의해 계산된 위치에 데이터가 저장된 테이블
  • 충돌
    • 서로 다른 키가 해시 함수에 의해 같은 주소로 계산되는 상황
    • 충돌을 일으키는 키를 '동의어'라고 함
  • 오버플로
    • 충돌에 의해 해당 주소에 더 이상 저장할 곳이 없는 상태
  • 이상적인 해싱
    • 충돌이 일어나지 않음
    • 해시 테이블의 크기가 크면 메모리 낭비가 심함 → 해시 테이블의 크기를 적절히 줄이고 충돌이 발생할 경우 처리방법(선형 조사법, 이차 조사법, 이중 해싱법, 체이닝)을 대비
  • 시간 복잡도
    • 최선: O(1)
    • 최악: O(n)

 

선형 조사법

  • 충돌이 일어나면 해시 테이블의 다음 위치에 저장
    • 순서대로 조사 → h(k), h(k) + 1, h(k) + 2, ...
    • 빈 곳이 있으면 저장
  • 탐색하려는 키가 해시 테이블에 있음에도 불구하고 빈 버킷에 의해 제대로 탐색되지 않는 경우 → 삭제 연산시 빈 버킷을 두 가지로 분류(한번도 사용 안 한 버킷, 삭제된 빈 버킷)

 

이차 조사법

  • 선형 조사법의 경우 충돌이 발생한 위치에서 항목들이 집중되는 군집화 현상을 볼 수 있는데, 이차 조사법을 통해 이를 완화할 수 있음
  • 충돌이 발생하면, 다음에 조사할 위치를 다음 식에 의해 결정 → (h(k) + i*i) % M for i = 0, 1, ... , M-1
  • 조사 위치: h(key), h(key) + 1, h(key) + 4, h(key) + 9, ...
  • 해시 함수 값이 같으면 다음 위치도 동일

 

이중 해싱법

  • 재해싱
  • 충돌이 발생하면, 다른 해시 함수를 이용해 다음 위치 계산
  • 해시 함수 값이 같아도 키가 다르면 다른 위치 가능

 

체이닝

  • 하나의 버킷에 여러 개의 레코드를 저장할 수 있도록 하는 방법
  • 버킷은 연결 리스트로 구현

 

체이닝을 이용한 해시 맵

class Node:  # 연결 리스트를 위한 노드
    def __init__(self, item, link=None):
        self.data = item
        self.link = link


class Entry:  # 노드의 데이터 부분에 저장
    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __str__(self):
        return str("{}:{}".format(self.key, self.value))


class HashChainMap:  # 해시 체인 맵
    def __init__(self, M):
        self.table = [None]*M  # 크기가 M인 테이블을 만듦
        self.M = M

    def hashFn(self, key):  # 해시 함수
        sum = 0
        # 문자열의 모든 문자에 대해 아스키 코드 값을 모두 더함
        for c in key:
            sum = sum + ord(c)
        return sum % self.M

    def display(self, msg):  # 맵 출력
        print(msg)
        for idx in range(len(self.table)):
            node = self.table[idx]
            if node is not None:
                print("[{}] -> ".format(idx), end='')
                while node is not None:  # None이 아닐 때까지
                    print(node.data, end=' -> ')
                    node = node.link
                print()

    def search(self, key):  # 탐색 연산
        idx = self.hashFn(key)  # 해시 주소 계산
        node = self.table[idx]
        while node is not None:  # None이 아닐 때까지
            if node.data.key == key:  # 탐색하고 있는 레코드를 찾음
                return node.data
            node = node.link
        return None

    def insert(self, key, value):  # 삽입 연산
        idx = self.hashFn(key)  # 해시 주소 계산
        self.table[idx] = Node(Entry(key, value), self.table[idx])  # 전단 삽입

    def delete(self, key):  # 삭제 연산
        idx = self.hashFn(key)  # 해시 주소 계산
        node = self.table[idx]
        before = None
        while node is not None:  # None이 아닐 때까지
            if node.data.key == key:  # 삭제할 레코드를 찾음
                if before == None:  # 첫 번째 항목 삭제
                    self.table[idx] = node.link
                else:  # 두 번째 이후 항목 삭제
                    before.link = node.link
            before = node  # before 갱신
            node = node.link  # node 갱신

'Computer Science > Data Structures' 카테고리의 다른 글

Data Structures : 탐색 트리  (0) 2023.02.18
Data Structures : 트리  (0) 2023.02.16
Data Structures : 연결된 구조  (0) 2023.02.10
Data Structures : 큐, 덱  (0) 2023.02.10
Data Structures : 스택  (0) 2023.02.09