자료구조, 시간 복잡도, 공간 복잡도 정리

자료구조, 시간 복잡도, 공간 복잡도 정리

알고리즘 효율성 분석과 주요 자료구조 총정리


목차

  1. 시간 복잡도와 공간 복잡도
  2. 배열 (Array)
  3. 연결리스트 (Linked List)
  4. 스택 (Stack)
  5. 큐 (Queue)

1. 시간 복잡도와 공간 복잡도

시간복잡도 (Time Complexity)

시간복잡도는 알고리즘이 실행되는 데 필요한 시간을 측정하는 방법으로, 입력 크기에 따른 연산 횟수를 나타냅니다.

점근적 표기법

표기법의미설명
빅오 (Big-O)최악의 경우 / 상한선알고리즘이 실행되는 데 필요한 최대 시간을 나타냄 (가장 많이 사용)
빅오메가 (Big-Ω)최선의 경우 / 하한선알고리즘이 실행되는 데 필요한 최소 시간을 나타냄
빅세타 (Big-Θ)평균 / 평균적 경우알고리즘의 상한과 하한이 동일할 때 사용 (점근적으로 동일한 상한과 하한)

대표적인 시간 복잡도

복잡도설명예시
O(1)상수 시간배열의 인덱스 접근
O(log n)로그 시간이진 검색
O(n)선형 시간배열 순회
O(n log n)선형로그 시간효율적인 정렬 알고리즘
O(n²)제곱 시간이중 반복문
O(2ⁿ)지수 시간부분집합 생성
O(n!)팩토리얼 시간순열 생성

공간 복잡도 (Space Complexity)

공간 복잡도는 알고리즘이 실행되는 동안 필요한 메모리 공간을 측정하는 방법으로, 입력 크기에 따른 메모리 사용량을 나타냅니다.

공간복잡도설명예시
O(1)상수 공간변수 몇 개만 사용하는 알고리즘
O(log n)로그 공간이진 검색, 특정 분할 정복 알고리즘
O(n)선형 공간입력 크기만큼의 배열 사용
O(n log n)선형로그 공간특정 정렬 알고리즘 (합병 정렬의 구현에 따라)
O(n²)제곱 공간2차원 배열, 인접 행렬
O(2ⁿ)지수 공간모든 부분집합 저장
O(n!)팩토리얼 공간모든 순열 저장

2. 배열 (Array)

정의 및 개념

배열은 동일한 타입의 데이터가 연속적인 메모리 공간에 저장되는 자료구조입니다. 인덱스를 통해 원소에 직접 접근이 가능합니다.

필수 연산

  • 인덱스를 통한 접근 (Access)
  • 원소 추가 (Insertion)
  • 원소 삭제 (Deletion)
  • 원소 검색 (Search)
  • 원소 수정 (Update)

구현 방법 (Python)

# Python에서는 리스트가 동적 배열로 구현되어 있음
# 배열 선언
arr = [0] * 10  # 크기가 10인 배열 초기화

# 접근
element = arr[5]  # 인덱스 5의 요소 접근

# 삽입
arr.append(100)  # 배열 끝에 요소 추가
arr.insert(2, 50)  # 인덱스 2 위치에 50 삽입

# 삭제
arr.pop()  # 마지막 요소 제거
arr.pop(3)  # 인덱스 3의 요소 제거
arr.remove(50)  # 값이 50인 첫 번째 요소 제거

# 검색
index = arr.index(100)  # 값이 100인 요소의 인덱스 찾기
exists = 100 in arr  # 값이 100인 요소가 존재하는지 확인

# 수정
arr[4] = 200  # 인덱스 4의 요소를 200으로 수정

시간복잡도 및 공간복잡도

연산시간복잡도 (최선)시간복잡도 (평균)시간복잡도 (최악)공간복잡도
선언O(n)O(n)O(n)O(n)
접근O(1)O(1)O(1)O(1)
검색O(1)O(n/2)O(n)O(1)
삽입 (끝)O(1)O(1)O(n) (재할당 시)O(1)
삽입 (중간)O(n)O(n)O(n)O(1)
삭제 (끝)O(1)O(1)O(1)O(1)
삭제 (중간)O(n)O(n)O(n)O(1)
수정O(1)O(1)O(1)O(1)

다른 자료구조와의 비교

장점: 인덱스를 통한 빠른 접근(O(1)), 구현이 간단

단점: 크기가 고정되어 있거나 동적 확장 시 비용 발생, 중간 삽입/삭제가 비효율적

연결리스트와 비교: 접근은 빠르지만 삽입/삭제는 연결리스트가 더 효율적


3. 연결리스트 (Linked List)

정의 및 개념

연결리스트는 각 노드가 데이터와 다음 노드를 가리키는 포인터로 구성되어 있는 선형 자료구조입니다. 메모리에 연속적으로 저장되지 않고, 각 노드가 다음 노드의 위치를 가리키는 방식으로 연결됩니다.

필수 연산

  • 노드 추가 (Insertion)
  • 노드 삭제 (Deletion)
  • 노드 검색 (Search)
  • 노드 순회 (Traversal)

구현 방법 (Python)

class Node:
    def __init__(self, data):
        self.data = data  # 노드 데이터
        self.next = None  # 다음 노드 참조

class LinkedList:
    def __init__(self):
        self.head = None  # 연결 리스트의 시작 노드(헤드)

    # 끝에 노드 추가
    def append(self, data):
        new_node = Node(data)

        if self.head is None:  # 빈 리스트인 경우
            self.head = new_node
            return

        current = self.head
        while current.next:  # 마지막 노드까지 이동
            current = current.next
        current.next = new_node

    # 중간에 노드 삽입
    def insert_at(self, position, data):
        if position == 0:  # 맨 앞에 삽입
            new_node = Node(data)
            new_node.next = self.head
            self.head = new_node
            return

        current = self.head
        for i in range(position-1):
            if current is None:
                return  # 위치가 리스트 범위를 벗어남
            current = current.next

        if current is None:
            return

        new_node = Node(data)
        new_node.next = current.next
        current.next = new_node

    # 노드 삭제
    def delete(self, data):
        if self.head is None:
            return

        if self.head.data == data:  # 삭제할 노드가 헤드인 경우
            self.head = self.head.next
            return

        current = self.head
        while current.next and current.next.data != data:
            current = current.next

        if current.next:  # 삭제할 노드를 찾은 경우
            current.next = current.next.next

    # 노드 검색
    def search(self, data):
        current = self.head
        position = 0

        while current:
            if current.data == data:
                return position
            current = current.next
            position += 1

        return -1  # 찾지 못한 경우

    # 리스트 출력
    def display(self):
        elements = []
        current = self.head

        while current:
            elements.append(current.data)
            current = current.next

        return elements

시간복잡도 및 공간복잡도

연산시간복잡도 (최선)시간복잡도 (평균)시간복잡도 (최악)공간복잡도
선언O(1)O(1)O(1)O(1)
접근O(1)O(n/2)O(n)O(1)
검색O(1)O(n/2)O(n)O(1)
삽입 (시작)O(1)O(1)O(1)O(1)
삽입 (끝)O(1)O(n)O(n)O(1)
삽입 (중간)O(1)O(n/2)O(n)O(1)
삭제 (시작)O(1)O(1)O(1)O(1)
삭제 (끝)O(n)O(n)O(n)O(1)
삭제 (중간)O(1)O(n/2)O(n)O(1)
수정O(1)O(n/2)O(n)O(1)

다른 자료구조와의 비교

장점: 동적 크기 조정, 효율적인 삽입/삭제 (위치를 알고 있는 경우)

단점: 임의 접근 불가능, 순차적 접근으로 인한 검색 비효율, 추가 메모리 사용 (포인터)

배열과 비교: 삽입/삭제는 효율적이지만 접근은 배열보다 느림

종류: 단일 연결 리스트, 이중 연결 리스트, 원형 연결 리스트


4. 스택 (Stack)

정의 및 개념

스택은 Last-In-First-Out(LIFO, 후입선출) 방식을 따르는 선형 자료구조입니다. 데이터의 삽입과 삭제가 한쪽 끝(맨 위)에서만 이루어집니다.

필수 연산

  • 삽입 (Push): 스택 맨 위에 요소 추가
  • 삭제 (Pop): 스택 맨 위 요소 제거 및 반환
  • 조회 (Peek/Top): 스택 맨 위 요소 반환 (제거하지 않음)
  • 비었는지 확인 (isEmpty): 스택이 비어있는지 확인

구현 방법 (Python)

리스트를 이용한 구현

class Stack:
    def __init__(self):
        self.items = []  # 스택의 요소를 저장할 리스트

    def push(self, item):
        self.items.append(item)  # 요소 추가

    def pop(self):
        if not self.is_empty():
            return self.items.pop()  # 맨 위 요소 제거 및 반환
        return None

    def peek(self):
        if not self.is_empty():
            return self.items[-1]  # 맨 위 요소 반환 (제거하지 않음)
        return None

    def is_empty(self):
        return len(self.items) == 0  # 스택이 비어있는지 확인

    def size(self):
        return len(self.items)  # 스택 크기 반환

연결리스트를 이용한 구현

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class Stack:
    def __init__(self):
        self.top = None  # 스택의 맨 위 노드
        self.size = 0  # 스택 크기

    def push(self, data):
        new_node = Node(data)
        new_node.next = self.top
        self.top = new_node
        self.size += 1

    def pop(self):
        if self.is_empty():
            return None

        data = self.top.data
        self.top = self.top.next
        self.size -= 1
        return data

    def peek(self):
        if self.is_empty():
            return None
        return self.top.data

    def is_empty(self):
        return self.top is None

    def get_size(self):
        return self.size

시간복잡도 및 공간복잡도

연산시간복잡도 (최선)시간복잡도 (평균)시간복잡도 (최악)공간복잡도
선언O(1)O(1)O(1)O(1)
PushO(1)O(1)O(1)O(1)
PopO(1)O(1)O(1)O(1)
PeekO(1)O(1)O(1)O(1)
검색O(1)O(n/2)O(n)O(1)

다른 자료구조와의 비교

장점: 구현이 간단하고 빠른 삽입/삭제 (O(1))

단점: 중간 요소에 직접 접근 불가, 검색에 비효율적

큐와의 비교: 스택은 LIFO, 큐는 FIFO 방식

활용: 함수 호출 스택, 괄호 검사, 역순 문자열, DFS 등에 사용


5. 큐 (Queue)

정의 및 개념

큐는 First-In-First-Out(FIFO, 선입선출) 방식을 따르는 선형 자료구조입니다. 한쪽 끝(rear)에서 삽입하고 다른 쪽 끝(front)에서 삭제가 이루어집니다.

필수 연산

  • 삽입 (Enqueue): 큐의 뒤쪽(rear)에 요소 추가
  • 삭제 (Dequeue): 큐의 앞쪽(front)에서 요소 제거 및 반환
  • 조회 (Peek/Front): 큐의 앞쪽 요소 반환 (제거하지 않음)
  • 비었는지 확인 (isEmpty): 큐가 비어있는지 확인

구현 방법 (Python)

리스트를 이용한 구현

class Queue:
    def __init__(self):
        self.items = []  # 큐의 요소를 저장할 리스트

    def enqueue(self, item):
        self.items.append(item)  # 뒤쪽에 요소 추가

    def dequeue(self):
        if not self.is_empty():
            return self.items.pop(0)  # 앞쪽에서 요소 제거 및 반환
        return None

    def peek(self):
        if not self.is_empty():
            return self.items[0]  # 앞쪽 요소 반환 (제거하지 않음)
        return None

    def is_empty(self):
        return len(self.items) == 0  # 큐가 비어있는지 확인

    def size(self):
        return len(self.items)  # 큐 크기 반환

연결리스트를 이용한 구현

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class Queue:
    def __init__(self):
        self.front = None  # 큐의 앞쪽 노드
        self.rear = None   # 큐의 뒤쪽 노드
        self.size = 0      # 큐 크기

    def enqueue(self, data):
        new_node = Node(data)

        if self.is_empty():  # 빈 큐인 경우
            self.front = new_node
            self.rear = new_node
        else:
            self.rear.next = new_node
            self.rear = new_node

        self.size += 1

    def dequeue(self):
        if self.is_empty():
            return None

        data = self.front.data
        self.front = self.front.next

        if self.front is None:  # 마지막 요소를 제거한 경우
            self.rear = None

        self.size -= 1
        return data

    def peek(self):
        if self.is_empty():
            return None
        return self.front.data

    def is_empty(self):
        return self.front is None

    def get_size(self):
        return self.size

시간복잡도 및 공간복잡도

연산시간복잡도 (최선)시간복잡도 (평균)시간복잡도 (최악)공간복잡도
선언O(1)O(1)O(1)O(1)
EnqueueO(1)O(1)O(1)O(1)
Dequeue (리스트)O(n)O(n)O(n)O(1)
Dequeue (연결리스트)O(1)O(1)O(1)O(1)
PeekO(1)O(1)O(1)O(1)
검색O(1)O(n/2)O(n)O(1)

다른 자료구조와의 비교

장점: 순서가 보장되는 처리, 연결리스트로 구현 시 빠른 삽입/삭제

단점: 중간 요소에 직접 접근 불가, 리스트로 구현 시 dequeue 연산 비효율

스택과의 비교: 큐는 FIFO, 스택은 LIFO 방식

종류: 일반 큐, 원형 큐, 우선순위 큐, 덱(Deque)

활용: BFS, 작업 스케줄링, 프린터 대기열, 버퍼 등에 사용


자료구조와 알고리즘 복잡도 정리