최단 경로 문제

  • 두 노드를 잇는 가장 짧은 경로를 찾는 문제
  • 가중치 그래프에서 간선의 가중치 합이 최소가 되도록 하는 경로를 찾는 것이 목적

최단 경로 문제 종류

  1. 단일 출발 및 단일 도착 최단 경로 문제
    • 그래프 내의 특정 노드 u에서 출발, 또 다른 특정 노드 v에 도착하는 가장 짧은 경로 찾는 문제
  2. 단일 출발 최단 경로 문제
    • 그래프 내의 특정 노드 u와 그래프 내 다른 모든 노드 각각의 가장 짧은 경로를 찾는 문제
  3. 전체 쌍 최단 경로
    • 그래프 내의 모든 노드 쌍 (u, v)에 대한 최단 경로를 찾는 문제

다익스트라 알고리즘

다익스트라 알고리즘은 하나의 정점에서 다른 모든 정점 간의 각각 가장 짧은 거리를 구하는 문제에 적절

 

다익스트라 알고리즘 로직

  • 첫 정점을 기준으로 연결되어 있는 정점들을 추가해 가며 최단 거리를 갱신하는 기법
  • 다익스트라 알고리즘은 BFS와 유사
    •  첫 정점부터 각 노드 간의 거리를 저장하는 배열을 만든 후, 첫 정점의 인접 노드 간의 거리부터 먼저 계산하면서, 첫 정점부터 해당 노드 간의 가장 짧은 거리를 해당 배열에 업데이트
  • 우선순위 큐를 활용 : 우선순위 큐는 Min Heap방식을 활용해서, 현재 가장 짧은 거리를 가진 노드 정보를 먼저 꺼내게 됨
    1. 첫 정점을 기준으로 배열을 선언하여 첫 정점에서 각 정점까지의 거리를 저장
      • 초기에는 첫 정점의 거리는 0, 나머지는 무한대로 저장(inf)
      • 우선순위 큐에 (첫 정점, 0)만 먼저 넣음
    2. 우선순위 큐에서 노드를 꺼냄
      • 처음에는 첫 정점만 저장되어 있으므로, 첫 정점만 꺼내짐
      • 첫 정점에 인접한 노드들 각각에 대해, 첫 정점에서 각 노드로 가는 거리와 현재 배열에 저장되어 있는 첫 정점에서 각 정점까지의 거리를 비교
      • 배열에 저장되어 있는 거리보다, 첫 정점에서 해당 노드로 가는 거리가 더 짧을 경우 배열에 해당 노드의 거리를 업데이트
      • 배열에 해당 노드의 거리가 업데이트된 경우, 우선순위 큐에 넣는다
        -> 결과적으로 너비 우선 탐색과 유사하게, 첫 정점에 인접한 노드들을 순차적으로 방문하게 됨
        -> 만약 배열에 기록된 현재까지 발견된 가장 짧은 거리보다 더 긴 거리를 가진 경우에 해당 노드와 인접한 노드 간의 거리 계산을 하지 않음
    3. 2번의 과정을 우선순위 큐에 꺼낼 노드가 없을 때까지 반복

알고리즘 구현

graph = {
    'A': {'B': 8, 'C': 1, 'D': 2},
    'B': {},
    'C': {'B': 5, 'D': 2},
    'D': {'E': 3, 'F': 5},
    'E': {'F': 1},
    'F': {'A': 5}
}
import heapq

def dijkstra(graph, start):
    distances = { node: float('inf') for node in graph }
    distances[start] = 0
    pq = []
    heapq.heappush(pq, [distances[start], start])
    
    while pq:
        current_distance, current_node = heapq.heappop(pq)
        
        if current_distance > distances[current_node]:
            continue
        
        for adjacent, weight in graph[current_node].items():
            distance = current_distance + weight
            
            if distance < distances[adjacent]:
                distances[adjacent] = distance
                heapq.heappush(pq, [distance, adjacent])
                
    return distances

 

*A부터 F까지의 최단 경로 출력 코드

import heapq

def dijkstra(graph, start, end):
    distances = {node: [float('inf'), start] for node in graph}
    distances[start][0] = 0
    pq = []
    heapq.heappush(pq, [distances[start][0], start])
    
    while pq:
        current_distance, current_node = heapq.heappop(pq)
        
        if current_distance > distances[current_node][0]:
            continue
            
        for adjacent, weight in graph[current_node].items():
            distance = current_distance + weight
            
            if distance < distances[adjacent][0]:
                distances[adjacent] = [distance, current_node]
                heapq.heappush(pq, [distance, adjacent])
    
    path = end
    path_output = [end]
    while distances[path][1] != start:
        path_output.append(distances[path][1])
        path = distances[path][1]
    path_output.append(start)
    path_output = list(reversed(path_output))
    
    return path_output
    
result = dijkstra(graph, 'A', 'F')
print(result)
print("->".join(result))

"""

['A', 'D', 'E', 'F']
A->D->E->F

"""

 

시간 복잡도

  • 과정 1: 각 노드마다 인접한 노드 간선(E)들을 모두 검사 => O(E)
  • 과정 2: 우선순위 큐에 노드/거리 정보를 넣고 삭제하는 과정 => O(ElogE)
  • 과정 1 + 과정 2의 시간을 합치면 결국 O(ElogE)

'Algorithm > 탐색' 카테고리의 다른 글

BFS와 DFS  (0) 2020.03.26
이진탐색(Binary search)  (0) 2020.03.13

BFS와 DFS란?

  • 너비 우선 탐색(Breadth First Search) : 정점들과 같은 레벨에 있는 노드들을 먼저 탐색
  • 깊이 우선 탐색(Depth First Search) : 정점의 자식들을 먼저 탐색
  • 시간 복잡도는 정점의 수(V)와 간선의 수(E)를 더한 O(V + E)
  • 파이썬에서는 딕셔너리리스트 자료구조를 활용해서 구현 가능
graph = dict()

graph['A'] = ['B', 'C']
graph['B'] = ['A', 'D']
graph['C'] = ['A', 'G', 'H', 'I']
graph['D'] = ['B', 'E', 'F']
graph['E'] = ['D']
graph['F'] = ['D']
graph['G'] = ['C']
graph['H'] = ['C']
graph['I'] = ['C', 'J']
graph['J'] = ['I']

너비 우선 탐색(BFS)

- 의 역할을 하는 visited와 need_visit을 이용해 구현

- 방문한 노드는 visited에, 방문할 노드는 need_visit에 추가

def bfs(graph, start_node):
    visited = list()
    need_visit = list()
    
    need_visit.append(start_node)
    
    while need_visit:
        node = need_visit.pop(0)
        if node not in visited:
            visited.appned(node)
            need_visit.extend(graph[node])
    
    return visited

 

깊이 우선 탐색(DFS)

- 의 역할을 하는 visited, 스택의 역할을 하는 need_visit을 이용해 구현

def dfs(graph, start_node):
    visited = list()
    need_visit = list()
    
    need_visit.append(start_node)
    
    while need_visit:
        node = need_visit.pop()
        if node not in visited:
            visited.append(node)
            need_visit.extend(graph[node])
    
    return visited

 

'Algorithm > 탐색' 카테고리의 다른 글

최단 경로 알고리즘 - 다익스트라(Dijkstra)  (0) 2020.03.27
이진탐색(Binary search)  (0) 2020.03.13

병합 정렬이란?

재귀 용법을 활용한 정렬 알고리즘으로 배열을 절반으로 잘라나가고 이를 다시 정렬된 배열의 형태로 합병하며 정렬함.

 

알고리즘 구현

병합 정렬은 배열을 나누는 함수, 나누어진 배열을 하나로 합치는 함수 2가지를 구현해야 함

 

1. merge 함수

def merge(left, right):
    merged = list()
    left_index, right_index = 0, 0
    
    while len(left) > left_index and len(right) > right_index:
        if left[left_index] > right[right_index]:
            merged.append(right[right_index])
            right_index += 1
        else:
            merged.append(left[left_index])
            left_index += 1
    
    while len(left) > left_index:
        merged.append(left[left_index])
        left_index += 1
        
    while len(right) > right_index:
        merged.append(right[right_index])
        right_index += 1
        
    return merged

 

2. merge_split 함수(실질적인 병합 정렬 역할의 함수)

def merge_split(data):
    if len(data) <= 1:
        return data
    
    mid = len(data) // 2
    left = merge_split(data[:mid])
    right = merge_split(data[mid:])
    
    return merge(left, right)

 

시간 복잡도

각 단계별로 O(N)으로 실행되고, 그 단계들은 logN개만큼 만들어지므로 O(logN)의 시간 복잡도를 가진다.

결국 이 모든 과정을 통합하면 O(NlogN)이라는 시간 복잡도가 나오게 된다.

'Algorithm > 정렬' 카테고리의 다른 글

퀵 정렬(Quick sort)  (0) 2020.03.23
삽입정렬(Insertion sort)  (0) 2020.03.23

힙이란?

데이터에서 최댓값 또는 최솟값을 빠르게 찾기 위해 고안된 완전 이진트리

 

힙을 사용하는 이유

  • 배열에 데이터를 넣고 최댓값과 최솟값을 찾으려면 O(N)이 걸리지만, 힙에 데이터를 넣고 최대값 또는 최소값을 찾으면 O(logN)이 걸림
  • 우선순위 큐와 같이 최대값 도는 최소값을 빠르게 찾아야 하는 자료구조 및 알고리즘 구현 등에 활용됨

힙 구조

  • 힙은 최댓값을 구하기 위한 최대 힙(Max Heap), 최솟값을 구하기 위한 최소 힙(Min Heap)으로 분류할 수 있음
  • 힙 자료구조의 조건
    • 각 노드의 값은 해당 노드의 자식 노드가 가진 값보다 크거나 같음(Max Heap)
    • 완전 이진트리 형태를 가짐

힙과 이진 탐색 트리의 공통점과 차이점

  • 공통점 : 힙과 이진 탐색 트리는 모두 이진 트리
  • 차이점 :
    • 힙은 각 노드의 값이 자식 노드보다 크거나 같음(Max heap)
    • 이진 탐색 트리는 왼쪽 자식 노드의 값이 가장 작고, 그다음 부모 노드, 그 다음 오른쪽 자식 노드 값이 가장 큼
    • 이진 탐색 트리는 탐색을 위한 구조, 은 최댓값/최솟값 검색을 위한 구조

힙 구현(Max Heap)

  • 일반적으로 힙 구현 시 배열을 활용
  • 배열은 인덱스가 0번부터 시작하지만, 힙 구현에서는 편의상 root 노드의 인덱스 번호를 1로 지정
    • 부모 노드 인덱스 = 자식 노드 인덱스 // 2
    • 왼쪽 자식 노드 인덱스 = 부모 노드 인덱스 * 2
    • 오른쪽 자식 노드 인덱스 = 부모 노드 인덱스 * 2 + 1
class Heap:
    def __init__(self, data):
        self.heap_array = list()
        self.heap_array.append(None)
        self.heap_array.append(data)

 

데이터 삽입

- 배열의 마지막에 노드를 추가

- 삽입한 노드가 부모 노드보다 클 경우, 부모 노드와 삽입한 노드를 서로 바꿈

- 삽입한 노드가 루트 노드가 되거나, 부모 노드보다 값이 작거나 같을 때까지 반복

    def move_up(self, inserted_idx):
        if inserted_idx <= 1:
            return False
            
        parent_idx = inserted_idx // 2
        
        if self.heap_array[inserted_idx] > self.heap_array[parent_idx]:
            return True
        else:
            return False

    def insert(self, data):
        if len(self.heap_array) == 0:
            self.heap_array.append(None)
            self.heap_array.append(data)
            return True
            
        self.heap_array.append(data)
        inserted_idx = len(self.heap_array) - 1
        
        while self.move_up(inserted_idx):
            parent_idx = inserted_idx // 2
            self.heap_array[inserted_idx], self.heap_array[parent_idx] = self.heap_array[parent_idx], self.heap_array[inserted_idx]
            inserted_idx = parent_idx
        
        return True

 

데이터 삭제

- root 노드를 반환하고, 가장 마지막 노드를 root 노드로 옮긴다

- root 노드의 값보다 자식 노드의 값이 크다면 서로 바꾼다(반복)

    def move_down(self, popped_idx):
        left_child_idx = popped_idx * 2
        right_child_idx = popped_idx * 2 + 1
        
        if left_child_idx >= len(self.heap_array):
            return False
        elif right_child_idx >= len(self.heap_array):
            if self.heap_array[popped_idx] < self.heap_array[left_child_idx):
                return True
            else:
                return False
        else:
            if self.heap_array[left_child_idx] > self.heap_array[right_child_idx]:
                if self.heap_array[popped_idx] < self.heap_array[left_child_idx]:
                    return True
                else:
                    return False
            else:
                if self.heap_array[popped_idx] < self.heap_array[right_child_idx]:
                    return True
                else:
                    return False

    def pop(self):
        if len(self.heap_array) <= 1:
            return None
        
        returned_data = self.heap_array[1]
        self.heap_array[1] = self.heap_array[-1]
        del self.heap_array[-1]
        popped_idx = 1
        
        while self.move_down(popped_idx):
            left_child_idx = popped_idx * 2
            right_child_idx = popped_idx * 2 + 1
            
            if right_child_idx >= len(self.heap_array):
                if self.heap_array[popped_idx] < self.heap_array[left_child_idx]:
                    self.heap_array[popped_idx], self.heap_array[left_child_idx] = self.heap_array[left_child_idx], self.heap_array[popped_idx]
                    popped_idx = left_child_idx
            else:
                if self.heap_array[left_child_idx] > self.heap_array[right_idx]:
                    if self.heap_array[popped_idx] < self.heap_array[left_child_idx]:
                        self.heap_array[popped_idx], self.heap_array[left_child_idx] = self.heap_array[left_child_idx], self.heap_array[popped_idx]
                        popped_idx = left_child_idx
                else:
                    if self.heap_array[popped_idx] < self.heap_array[right_child_idx]:
                        self.heap_array[popped_idx], self.heap_array[right_child_idx] = self.heap_array[right_child_idx], self.heap_array[popped_idx]
                        popped_idx = right_child_idx
        
        return returned_data
                        
                        
                        

해쉬와 관련된 용어들

  • 해쉬(Hash) : 임의의 값을 고정 길이의 값으로 변환하는 것
  • 해쉬 테이블(Hash table) : 키 값의 연산에 의해 직접 접근이 가능한 데이터 구조
  • 해싱 함수(Hashing function) : Key에 대해 산술 연산을 이용해 데이터 위치를 찾을 수 있는 함수
  • 해쉬 값 or 해쉬 주소(Hash address) : Key를 해싱 함수로 연산한 결과 값, 이를 기반으로 해당 Key에 대한 데이터 위치를 일관성 있게 찾을 수 있음.
  • 슬롯(Slot) : 한 개의 데이터를 저장할 수 있는 공간

해쉬 테이블의 장단점과 주요 용도

  • 장점
    • 데이터 저장/읽기 속도가 빠름
    • 해쉬는 키에 대한 데이터가 있는지 확인이 쉬움
  • 단점
    • 저장공간이 많이 필요함 
    • 여러 키(Key)에 해당하는 주소가 동일할 경우 충돌을 해결하기 위해 별도의 자료구조가 필요함
  • 주요 용도
    • 검색이 많이 필요한 경우
    • 저장, 삭제, 읽기가 빈번한 경우
    • 캐쉬 구현 시(중복 확인이 쉽기 때문에)

충돌(Collision) 해결 알고리즘

  • Chaining 기법
    • 개방 해싱 또는 Open Hashing 기법 중 하나 : 해쉬 테이블 저장공간 외의 공간을 활용하는 기법
    • 충돌이 일어나면, 링크드 리스트로 데이터를 추가하여 저장하는 기법
import hashlib

hash_table = list([0 for i in range(8)])

def get_key(data):
    hash_obj = hashlib.sha256()
    hash_obj.update(data.encode())
    hex_dig = hash_obj.hexdigest()
    return int(hex_dig, 16)

def hash_function(key):
    return key % 8
    
def save_data(data, value):
    index_key = get_key(data)
    hash_address = hash_function(index_key)
    
    if hash_table[hash_address] != 0:
    	for index in range(len(hash_table[hash_address])):
            if hash_table[hash_address][index][0] == index_key:
                hash_table[hash_address][index][1] = value
                return
        hash_table[hash_address].append([index_key, value])
    else:
        hash_table[hash_address] = [[index_key, value]]
        
def read_data(data):
    index_key = get_key(data)
    hash_address = hash_function(index_key)
    
    if hash_table[hash_address] != 0:
        for index in range(len(hash_table[hash_address])):
            if hash_table[hash_address][index][0] == index_key:
                return hash_table[hash_address][index][1]
        return None
    else:
        return None
  • Linear Probing 기법
    • 폐쇄 해싱 또는 Close Hashing 기법 중 하나 : 해쉬 테이블 저장공간 안에서 충돌 문제를 해결하는 기법
    • 충돌이 일어나면, 해당 Hash address의 다음 address부터 맨 처음 나오는 빈 공간에 저장하는 기법
import hashlib

hash_table = list([0 for i in range(8)])

def get_key(data):
    hash_obj = hashlib.sha256()
    hash_obj.update(data.encode())
    hex_dig = hash_obj.hexdigest()
    return int(hex_dig, 16)
    
def hash_function(key):
    return key % 8
    
def save_data(data, value):
    index_key = get_key(data)
    hash_address = hash_function(index_key)
    
    if hash_table[hash_address] != 0:
        for index in range(hash_address, len(hash_table)):
            if hash_table[index] == 0:
                hash_table[index] = [index_key, value]
                return
            elif hash_table[index][0] == index_key:
                hash_table[index][1] = value
               	return
    else:
        hash_table[hash_address] = [index_key, value]

def read_data(data):
    index_key = get_key(data)
    hash_address = hash_function(index_key)
    
    if hash_table[hash_address] != 0:
        for index in range(hash_address, len(hash_table)):
            if hash_table[index] == 0:
                return None
            elif hash_table[index][0] == index_key:
                return hash_table[index][1]
        return None
    else:
        return None

 

시간 복잡도

  • 일반적인 경우(Collision이 없는 경우) O(1)
  • 최악의 경우(Collision이 모두 발생하는 경우) O(N)

'Data Structure' 카테고리의 다른 글

힙(Heap)  (0) 2020.03.25
이진트리(Binary Tree), 이진 탐색 트리(Binary Search Tree)  (0) 2020.03.24

이진트리와 이진 탐색 트리

  • 이진트리: 노드의 최대 Branch가 2인 트리
  • 이진 탐색 트리: 이진트리이면서 하나의 노드를 기준으로 왼쪽 자식 노드는 해당 노드보다 작은 값, 오른쪽 자식 노드는 큰 값을 가지는 트리

이진 탐색 트리의 장점과 주요 용도

데이터 검색에 사용되며 탐색 속도를 개선할 수 있다는 장점을 가지고 있다.

 

이진 탐색 트리

1. 데이터 삽입

class Node:
    def __init__(self, value):
    	self.value = value
        self.left = None
        self.right = None

class BST:
	...
    
    def insert(self, value):
    	self.current_node = self.head
        while True:
            if value < self.current_node.value:
                if self.current_node.left != None:
                    self.current_node = self.current_node.left
                else:
                    self.current_node.left = Node(value)
                    break
            else:
                if self.current_node.right != None:
                    self.current_node = self.current_node.right
                else:
                    self.current_node.right = Node(value)
                    break

 

2. 데이터 조회

class BST:
	...
    
    def search(self, value):
    	self.current_node = self.head
        while self.current_node:
            if self.current_node.value == value:
            	return True
            elif value < self.current_node.value:
                self.current_node = self.current_node.left
            else:
                self.current_node = self.current_node.right
        return False

 

3. 데이터 삭제

이진 탐색 트리에서 가장 신경 써야 할 부분이 바로 데이터 삭제이다.

데이터 삭제할 때 생각해야 할 경우의 수는 크게 3가지로 나눌 수 있다.

  • Leaf Node 삭제
    - 삭제할 Node의 Parent Node가 삭제할 Node를 가리키지 않도록 한다.
  • Child Node를 한 개 가지는 Node 삭제
    - 삭제할 Node의 Parent Node가 삭제할 Node의 Child Node를 가리키도록 한다.
  • Child Node를 두 개 가지는 Node 삭제
    - 삭제할 Node의 오른쪽 자식 중, 가장 작은 값을 삭제할 Node의 Parent Node가 가리키도록 한다.
    - 삭제할 Node의 왼쪽 자식 중, 가장 큰 값을 삭제할 Node의 Parent Node가 가리키도록 한다.
class BST:
	...
    
    def delete(self, value):
    	searched = False
        self.current_node = self.head
        self.parent = self.head
        while self.current_node:
            if value == self.current_node.value:
            	searched = True
                break
            elif value < self.current_node.value:
            	self.parent = self.current_node
                self.current_node = self.current_node.left
            else:
                self.parent = self.current_node
                self.current_node = self.currnet_node.right

        if searched == False:
            return False

        # 1. Leaf Node 삭제
        if self.current_node.left == None and self.current_node.right == None:
            if value < self.parent.value:
                self.parent.left = None
            else:
                self.parent.right = None
            del self.current_node
        # 2. Child Node를 한 개 가지는 Node를 삭제
        elif self.current_node.left != None and self.current_node.right == None:
            if value < self.parent.value:
                self.parent.left = self.current_node.left
            else:
                self.parent.right = self.current_node.left
            del self.current_node
        elif self.current_node.left == None and self.current_node.right != None:
            if value < self.parent.value:
                self.parent.left = self.current_node.right
            else:
                self.parent.right = self.current_node.right
            del self.current_node
        # 3. Child Node를 두 개 모두 가지는 Node를 삭제
        elif self.current_node.left != None and self.current_node.right != None:
            if value < self.parent.value:
                self.change_node = self.current_node.right
                self.change_node_parent = self.current_node.right
                while self.change_node.left != None:
                    self.change_node_parent = self.change_node
                    self.change_node = self.change_node.left
                if self.change_node.right != None:
                    self.change_node_parent.left = self.change_node.right
                else:
                    self.change_node_parent.left = None
                self.parent.left = self.change_node
                self.change_node.left = self.current_node.left
                self.change_node.right = self.current_node.right
            else:
                self.change_node = self.current_node.right
                self.change_node_parent = self.current_node.right
                while self.change_node.left != None:
                    self.change_node_parent = self.change_node
                    self.change_node = self.change_node.left
                if self.change_node.right != None:
                    self.change_node_parent.left = self.change_node.right
                else:
                    self.change_node_parent.left = None
                self.parent.right = self.change_node
                self.change_node.left = self.current_node.left
                self.change_node.right = self.current_node.right
            del self.current_node
        return True

'Data Structure' 카테고리의 다른 글

힙(Heap)  (0) 2020.03.25
해쉬 테이블(Hash table)  (0) 2020.03.25

1. 퀵 정렬이란?

  • 기준점(Pivot)을 정해서, 기준점보다 작은 데이터는 왼쪽, 큰 에디터는 오른쪽으로 모아서 정렬을 하는 것
  • 각 왼쪽, 오른쪽으로 나눈 것은 재귀 용법을 통해 다시 왼쪽, 오른쪽으로 나눔
  • 반환하는 값은 [정렬된 왼쪽 리스트] + [Pivot] + [정렬된 오른쪽 리스트]

2. 알고리즘 구현

def qsort(data):
    if len(data) <= 1:
        return data
    left, right = list(), list()
    pivot = data[0]
    for index in range(1, len(data)):
    	if pivot > data[index]:
            left.append(data[index])
        else:
            right.append(data[index])
    return qsort(left) + [pivot] + qsort(right)

 

파이썬의 list comprehension을 사용한다면 위의 코드보다 짧게 구현이 가능해진다.

def qsort(data):
    if len(data) <= 1:
    	return data
    pivot = data[0]
    left = [ item for item in data[1:] if pivot > item ]
    right = [ item for item in data[1:] if pivot <= item ]
    return qsort(left) + [pivot] + qsort(right)

 

3. 시간복잡도

일반적으로는 O(N logN)이지만, 최악의 경우인 맨 처음 pivot이 배열 내에서 가장 크거나 가장 작으면 모든 데이터를 비교하는 상황이 나오므로 O(N^2)이 될 수 있다.

'Algorithm > 정렬' 카테고리의 다른 글

병합 정렬(Merge sort)  (0) 2020.03.25
삽입정렬(Insertion sort)  (0) 2020.03.23

1. 삽입정렬이란?

두번째 인덱스부터 시작하여 해당 인덱스 앞에 있는 데이터부터 비교해서 그 값이 더 크다면 값을 서로 교환.

 

2. 구현코드

def insertion_sort(data):
	for stand in range(len(data)-1):
    		for idx in range(stand+1, 0, -1):
            		if data[idx] < data[idx-1]:
                		data[idx], data[idx-1] = data[idx-1], data[idx]
                        else:
                		break
	return data

 

3. 시간복잡도

  • 최악의 경우: O(N^2)
  • 완전 정렬이 된 경우: O(N)

 

 

 

 

 

 

 

'Algorithm > 정렬' 카테고리의 다른 글

병합 정렬(Merge sort)  (0) 2020.03.25
퀵 정렬(Quick sort)  (0) 2020.03.23

예외의 종류

  • 문법적 에러
  • 런타임 에러

문법적 에러

  • Syntax Error : 잘못된 문법
    사용하는 IDE의 linter에 의해 체크됨
    *linter: 코드 스타일과 문법을 체크해줌.

런타임 에러

  • NameError : 참조 변수가 없을 때 발생
a = 1
b = 2
print(c)

"""
NameError: name 'c' is not defined
"""
  • ZeroDivisionError : 0으로 나누기를 수행할 경우 발생
print(50/0)

"""
ZeroDivisionError: division by zero
"""
  • IndexError : 인덱스 범위를 넘어서는 값을 참조하려 할 경우 발생
lt = [1, 2, 3]
print(lt[3])

"""
IndexError: list index out of range
"""
  • KeyError : 딕셔너리에서 없는 Key로 조회하려 할 경우 발생
user = {'name': 'Lee', 'address': 'Seoul'}
print(user['age'])

"""
KeyError: 'age'
"""
  • AttributeError : 모듈, 클래스에 있는 잘못된 속성을 사용할 경우 발생
class User:
    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name


lee = User('Lee')
print("이름:", lee.get_name())
print("나이:", lee.get_age())

"""
AttributeError: 'User' object has no attribute 'get_age'
"""
  • ValueError : 참조하려는 값이 없을 경우 발생
lt = [1, 4, 6, 11]
lt.remove(2)

"""
ValueError: list.remove(x): x not in list
"""
  • FileNotFoundError : 읽으려는 파일이 존재하지 않을 경우 발생
with open('abc.txt', 'r') as f:
    print(f)
    
"""
FileNotFoundError: [Errno 2] No such file or directory: 'abc.txt'
"""
  • TypeError : 서로 다른 데이터 타입의 데이터를 유효하지 않은 연산하려는 경우 발생
lt = [1, 2, 3]
x = "Hello"
print(lt + x)

"""
TypeError: can only concatenate list (not "str") to list
"""

예외처리

  • try : 에러가 발생할 가능성이 있는 코드를 실행
  • except : 에러 발생 시 실행할 코드를 담고 있음
  • else : 에러가 발생하지 않았을 경우 실행
  • finally : 에러가 발생하든 하지 않든 항상 실행
  • raise : 예외를 직접 발생시키기 위한 키워드
input_key = list(input().split())

x = [4, 10, 55]

try:
    if len(input_key) == 2 and input_key[0] == 'r':
        x.remove(int(input_key[1]))
    else:
        raise Exception("잘못된 명령입니다.")
except ValueError:
    print("존재하지 않는 값입니다.")
except Exception as e:
    print(e)
else:
    print("삭제 성공!!")
finally:
    print("실행 끝!")

"""
r 10
삭제 성공!!
실행 끝!

r 20
존재하지 않는 값입니다.
실행 끝!

a
잘못된 명령입니다.
실행 끝!
"""

'Programming Language > Python' 카테고리의 다른 글

Python - 파일 읽기, 파일 쓰기  (0) 2020.03.14
Python - 모듈, 패키지  (0) 2020.03.14
클래스(class)  (0) 2020.03.13
람다식  (0) 2020.03.13
*args, **kwargs  (0) 2020.03.13

파일 읽기

f = open('./test.txt', 'r')
contents = f.read()
print(contents)
f.close()

"""

출력문

abcdefgh
ijklmnop
qrstuvwx
yz

"""

open 메서드를 통해 파일과 연결되며, read 메서드로 파일 내 모든 데이터들을 읽어 들인다.

마지막으로 사용이 끝나면 반드시 close메서드로 리소스를 반환해줘야 한다.

with open('./test.txt', 'r') as f:
    c = f.read()
    print(iter(c))
    print(list(c))
    print(c)

"""

출력

<str_iterator object at 0x0000021837F7B348>
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', '\n', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', '\n', 'y', 'z']
abcdefgh
ijklmnop
qrstuvwx
yz

"""

매번 close 하기가 번거로우므로 위의 코드처럼 with 문을 써주면 자동으로 close 처리가 된다.

 

strip 메서드

with open('./test.txt', 'r') as f:
    for c in f:
        print(c)

"""

abcdefgh

ijklmnop

qrstuvwx

yz

"""

각 줄의 끝에 '\n'에 의해서 사이사이에 한 줄씩 띄어져 있다.

이를 해결하기 위해서 양쪽의 공백을 제거해주는 strip메서드를 사용한다.

with open('./test.txt', 'r') as f:
    for c in f:
        print(c.strip())
        
"""

abcdefgh
ijklmnop
qrstuvwx
yz

"""

readline 메서드

한 줄 단위로 읽으며, 파라미터로 숫자를 기입할 경우, 해당 숫자만큼의 문자를 읽어 들인다.

with open('./test.txt', 'r') as f:
    line = f.readline()
    while line:
        print(line, end='')
        line = f.readline()

"""
abcdefgh
ijklmnop
qrstuvwx
yz
"""

readlines 메서드

전체를 읽고 한 줄 단위로 리스트에 저장한다.

with open('./test.txt', 'r') as f:
    lines = f.readlines()
    print(lines)
    print()
    for line in lines:
        print(line, end='')
        
"""
['abcdefgh\n', 'ijklmnop\n', 'qrstuvwx\n', 'yz']

abcdefgh
ijklmnop
qrstuvwx
yz
"""

 

파일 쓰기

쓰기 모드('w') 또는 추가 모드('a')로 설정해주면 파일 쓰기를 할 수 있다.

from random import randint

with open('./result.txt', 'w') as f:
    for cnt in range(6):
        f.write(str(randint(50, 100)))
        f.write('\n')
        
        
"""
result.txt

54
64
91
96
67
76

"""

writelines

리스트를 파일로 저장

with open('./result.txt', 'w') as f:
    list = ['kim\n', 'park\n', 'lee\n']
    f.writelines(list)
    

"""
result.txt

kim
park
lee

"""

'Programming Language > Python' 카테고리의 다른 글

Python - 예외, 예외처리  (0) 2020.03.16
Python - 모듈, 패키지  (0) 2020.03.14
클래스(class)  (0) 2020.03.13
람다식  (0) 2020.03.13
*args, **kwargs  (0) 2020.03.13

+ Recent posts