본문 바로가기
Algorithm

[알고리즘 개념 정리] 자료구조 (자료구조 개념, array, list, linked list, stack, queue)

by 파이현 2025. 3. 4.
📋 목차
    1. 자료구조란
       - 자료구조란
       - 자료구조의 분류
    2. 선형 자료구조
       - 정적 자료구조: 배열
       - 동적 자료구조: 리스트, linked list, stack, queue
    3. 비선형 자료구조
       - 트리
       - 그래프
    4. 예시 문제
       - 백준 10828번 스택
       - 백준 10845번 큐

 


1️⃣ 자료구조란

1) 자료구조(Data Structure)

데이터를 효율적으로 사용할 수 있도록 데이터를 표현 및 관리하기 위한 구조

 

💡 자료구조, 왜 공부해야 할까요?
      : 사용할 수 있는 컴퓨팅 리소스가 제한되어 있기 때문!
  
  컴퓨터를 구성하는 핵심 요소는 4가지로 CPU(중앙처리장치), 메모리(주기억장치), 보조기억장치, 입출력 장치로 구성되어 있습니다. 이 핵심 요소 중 데이터는 보통 보조기억장치에 저장되는데 보조기억장치에 저장된 어떤 프로그램을 실행하면 보조기억장치에 저장된 데이터는 메모리로 옮겨지게 되고, 메모리에 로드된 데이터를 CPU가 가져와 프로그램을 실행하게 됩니다. 즉, 보조기억장치에서 주기억장치, 주기억장치에서 CPU로 데이터가 전송되는 것입니다.
  이때 메모리는 프로그램의 실행 속도를 결정하게 됩니다. 메모리를 효율적으로 사용하지 않는다면 프로그램의 실행 속도는 느려질 것이고, 메모리를 효율적으로 사용한다면 프로그램은 빠르게 실행될 것입니다. 이를 위해서는 프로그램에 알맞은 자료구조를 사용하는 것이 필수적이고, 자료구조를 명확히 이해한 후 적절히 사용하는 것이 중요합니다.
  파스칼을 개발한 컴퓨터 과학자 니클라우스 비르트는 '알고리즘 + 자료구조 = 프로그램'이라는 말을 남겼습니다. 프로그래밍이란 알고리즘을 잘 작성하고, 그에 맞는 자료구조를 잘 선택하는 것으로, 자료구조와 알고리즘을 잘하면 좋은 개발자로 성장할 수 있다고 합니다.

 

2) 자료구조의 분류

https://www.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS2832062046

 

자료구조는 크게 단순 자료구조복합 자료구조로 나눌 수 있습니다. `단순 자료구조`는 정수(int, double, long), 실수(float), 문자(char), 문자열(string) 등이 있습니다. 이는 프로그래밍 시 사용되는 기본적인 데이터 타입입니다. `복합 자료구조`는 단순 자료구조를 기반으로 만들어낸 배열, 스택, 트리 등과 같은 자료구조를 의미합니다. 복합 자료구조는 선형 자료구조비선형 자료구조로 나뉘는데요, 이름에서 알 수 있듯이 선형 자료구조는 자료를 구성하는 원소를 하나씩 순차적으로 나열시킨 자료구조입니다. 즉, 선형 자료구조는 하나의 자료 뒤에 하나의 자료가 존재하는 형태입니다. 반면 비선형 자료구조는 하나의 자료 뒤에 여러 개의 자료가 존재하는 형태로 앞뒤 자료들은 1:N, N:N의 관계를 가지게 됩니다.

 


 

2️⃣ 선형 자료구조

하나의 자료 뒤에 하나의 자료가 존재하는 자료구조로, 배열, Linked List, 스택, 큐가 있습니다. 이 네 가지는 정적 자료구조와 동적 자료구조로 나눌 수 있는데, 정적 자료구조는 크기가 고정되어 있는 자료구조이고, 동적 자료구조는 크기가 바뀔 수 있는 자료구조를 뜻합니다.

 

1) 정적 자료구조 (Static Data Structure)

배열 (Array)

  • 구성: element (요소) = index (순서대로 번호 부여) + value (각 요소의 값)
  • 특징
    • 하나의 데이터 타입을 저장하는 자료구조
    • 크기를 정해놓으면 변경할 수 없음
    • 파이썬에서는 array 모듈이나 numpy 모듈을 사용해 배열을 생성할 수 있음
    • 배열에서는 값을 삭제하면 해당 인덱스는 빈 상태가 됨
  • 공간 복잡도: O(n) -> n개의 요소가 배열에 존재하면 n개의 공간을 차지
  • 시간 복잡도
연산 시간 복잡도 설명
접근 O(1) 인덱스를 이용에 배열의 요소에 바로 접근
삽입 O(n) 배열의 중간에 삽입하려면 해당 요소 이후 모든 데이터를 이동시켜야 함
삭제 O(n) 배열이 꽉 찼을 경우, 크기 재조정이 필요해 O(n)의 시간이 소요될 수 있음
검색 O(n) 순차 검색이 필요해 최악의 경우 O(n) 시간이 소요됨

https://www.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS2832062046

 

 

2) 동적 자료구조 (Dynamic Data Structure)

 

리스트 (List)

  • 구성: element (요소) = index (순서대로 번호 부여) + value (각 요소의 값)
  • 특징
    • 다양한 데이터 타입을 혼합해 저장할 수 있음 (ex) list1 = [1, 'a', True, 3.14] # int, char, bool, float
    • 리스트에서 값을 삭제하면 뒤의 인덱스가 앞으로 당겨져 데이터가 항상 연속적으로 존재하게 됨
    • CRUD 함수를 사용해 동적으로 리스트에 요소를 삽입, 읽기, 수정, 삭제가 가능
      • Create
        • append(x): 리스트의 끝에 x를 추가하는 함수
        • insert(i,x): 인덱스 i 위치에 요소 x를 추가하는 함수
        • extend(iterable): 다른 iterable 객체의 모든 요소를 리스트에 추가하는 함수
      • Read
        • index(x): x가 처음 등장하는 인덱스를 반환하는 함수
        • count(x): x가 몇 번 등장하는지 세는 함수
      • Update
        • list[1] = 10: 인덱스를 사용해 수정하기
        • list1[2:4] = [20,40]: 슬라이싱을 사용해 수정하기
      • Delete
        • remove(x): 처음으로 등장하는 value인 x 삭제
        • pop(i): i번째 요소 삭제
        • clear(): 리스트의 모든 요소 삭제
    • 추가적인 유용한 메서드
      • sort(): 오름차순 정렬
      • reverse(): 순서 뒤집기
      • copy(): 얕은 복사
  • 공간 복잡도: O(n)
  • 시간 복잡도
연산 시간 복잡도 설명
접근 O(1) 인덱스를 이용에 리스트의 요소에 바로 접근
삽입 O(n) 리스트의 중간에 삽입하려면 해당 요소 이후 모든 데이터를 이동시켜야 함
삭제 O(n) 리스트가 꽉 찼을 경우, 크기 재조정이 필요해 O(n)의 시간이 소요될 수 있음
검색 O(n) 순차 검색이 필요해 최악의 경우 O(n) 시간이 소요됨
추가 O(1) 리스트 끝에 요소를 추가하는 건 O(1) 시간이 소요됨
슬라이싱 O(k) 슬라이싱 된 부분의 크기가 k이면 k만큼의 시간이 소요됨

 

 

✅  링크드 리스트 (Linked List)

  • 구성: 노드(Node) = 데이터(노드가 실제 저장하는 값) + 다음 노드(리스트에서 다음 노드를 가리키는 포인터)
  • 특징
    • 종류
      • 단일 연결 리스트: 각 노드가 다음 노드를 가리키고, 마지막 노드의 포인터는 None 혹은 null
      • 이중 연결 리스트: 각 노드가 이전 노드와 다음 노드를 모두 가리키는 방식으로, 양방향으로 탐색이 가능
      • 원형 연결 리스트: 마지막 노드가 첫 번째 노드를 가리키는 구조로, 단일 연결 리스트와 이중 연결 리스트는 원형 연결 리스트로 구현 가능
    • 임의 접근 불가능: 배열이나 리스트처럼 인덱스를 통한 빠른 접근이 불가능하고, 노드를 순차적으로 탐색해야 함
    • 삽입/삭제 효율성: 리스트의 중간에 요소를 삽입하거나 삭제할 때 최소 O(1)의 시간이 걸리고, 최대 O(n)의 시간이 소요됨
    • 연속된 메모리 공간 미사용: 배열처럼 연속된 메모리 공간을 사용하지 않고 각 노드가 포인터를 통해 연결함 
  • 공간 복잡도: O(n)
  • 시간 복잡도
연산 시간 복잡도 설명
접근 O(n) 인덱스를 통합 직접 접근이 불가능해 순차적으로 접근해야 함
삽입 O(1) 특정 노드를 가리키는 포인터만 있으면 바로 삽입이 가능 (단, 삽입 위치를 찾는데 시간이 소요될 수 있음)
삭제 O(1) 삭제하려는 노드의 포인터만 있으면 바로 삭제 가능 (단, 삭제 위치를 찾는데 시간이 소요될 수 있음)
검색 O(n) 값 검색 시에는 링크드 리스트를 순차적으로 탐색해야 함

 

 

단일 연결 리스트 ❘ 출처: https://www.geeksforgeeks.org/
이중 연결 리스트 ❘ 출처: https://www.geeksforgeeks.org/

 

원형 연결 리스트 ❘ 출처: https://www.geeksforgeeks.org/

 

 스택 (Stack)

  • 특징
    • `LIFO`: Last In First Out (후입선출, 마지막에 들어온 데이터가 먼저 나가는 방식)
    • 단방향 접근: 삽입과 삭제는 항상 스택의 상단에서만 이뤄짐
    • 동적 크기 조정: 크기가 가득 차면 크기를 늘리거나 재조정할 수 있고, 링크드 리스트로 구현된 스택은 크기에 제한이 없음
    • 스택 포인터: 스택을 추적하는 포인터는 현재 스택의 맨 위를 가리킴
  • 공간 복잡도: 스택의 크기가 n일 때, O(n)의 공간 복잡도를 가짐
  • 시간 복잡도
연산 시간 복잡도 설명
push O(1) 스택의 가장 위에 데이터를 추가하는 연산
pop O(1) 스택의 가장 위에서 데이터를 제거하고 반환하는 연산
peak O(1) 가장 위에 있는 데이터를 반환
isEmpty O(1) 스택이 비어있는지 확인하는 연산

 

스택 ❘ https://www.geeksforgeeks.org/stack-data-structure/

 

 큐(Queue)

  • 특징
    • `FIFO`: First In First Out (가장 먼저 들어온 데이터가 가장 먼저 나가는 자료구조)
    • 양쪽에서 삽입 삭제: 한쪽 끝에서는 삽입, 다른 쪽 끝에서는 삭제가 이뤄짐
    • 큐 포인터: 앞(front), 뒤(rear)를 가리키는 포인터 존재
  • 공간 복잡도: O(n)
  • 시간 복잡도
연산 시간 복잡도 (deque 사용 시) 설명
append O(1) 큐의 뒤쪽에 데이터를 추가
pop, popleft O(1) 큐에서 데이터를 제거하고 반환
front O(1) 맨 앞에 있는 데이터를 반환
isEmpty O(1) 스택이 비어있는지 확인하는 연산

큐 ❘ https://www.geeksforgeeks.org/queue-data-structure/

 

💡 deque란?
     : double-ended queue의 줄임말로, 앞과 뒤에서 데이터를 처리할 수 있는 양방향 queue 자료구조이다. 
deque ❘ 출처: https://blog.naver.com/sooftware/221516440423

     - collections.deque 모듈을 사용한다 (dq = deque())
     - append(x): deque의 마지막에 요소를 삽입
     - appendleft(x): deque의 앞쪽에 요소를 삽입
     - pop(): deque의 마지막에서 제거 및 반환
     - popleft(): deque의 앞쪽 요소를 제거 및 반환
     - rotate(): deque의 요소들을 하나씩 회전시킴

 


 

3️⃣ 비선형 자료구조

비선형 자료구조는 하나의 자료 뒤에 여러 개의 자료가 오는 형태로 트리와 그래프가 있습니다.

  트리 (Tree)

 

  • 구성
    • 노드(Node): 데이터를 저장하는 기본 요소
    • 간선(Edge): 노드를 연결하는 선
    • 루트 노드(Root Node): 트리의 시작점
    • 자식 노드(Child Node), 부모 노드(Parent Node): 계층 구조에서 연결된 노드
    • 리프 노드(Leaf Node): 자식이 없는 노드
    • 서브 트리(Subtree): 특정 노드와 그 하위 노드들의 집합
  • 특징
    • 하나의 루트 노드에서 출발해 계층적인 구조를 가짐
    • 사이클이 없어 어떤 노드에서 시작해도 자기 자신으로 돌아갈 수 없음
    • 노드의 개수가 n이라면, 간선의 개수는 n-1 (즉, 모든 노드는 연결되어 있음)
  • 공간 복잡도: O(n)
  • 시간 복잡도
    • 탐색: 선형 탐색일 때는 O(n), 이진 탐색 트리(BST)일 때는 O(log N)
    • 삽입, 삭제: O(log N)

 

  그래프 (Graph)

https://www.geeksforgeeks.org/introduction-to-graphs-data-structure-and-algorithm-tutorials/?ref=header_outind

  • 구성
    • 정점(Vertex, Node): 데이터를 저장하는 요소
    • 간선(Edge): 정점들을 연결하는 선
    • 인접 리스트(Adjacency List): 각 정점이 인접한 정점을 리스트로 저장
    • 인접 행렬(Adjacency Matrix): N x N 크기의 2차원 배열로 그래프 표현
  • 특징
    • 정점과 간선으로 구성된 자료구조
    • 방향 그래프(Directed Graph)와 무방향 그래프(Undirected Graph)가 있음
    • 가중치 그래프(Weighted Graph): 간선에 가중치가 있음
    • 사이클이 있을 수도, 없을 수도 있음
  • 공간 복잡도
    • 인접 리스트: O(V + E) (정점 수 V, 간선 수 E)
    • 인접 행렬: O(V²) (정점 수 V)
  • 시간 복잡도
    • DFS (깊이 우선 탐색): O(V + E)
    • BFS (너비 우선 탐색): O(V + E)
    • 다익스트라 (최단 경로 알고리즘): O((V + E) log V) (우선순위 큐 사용 시)
    • 플로이드-워셜 (모든 정점 간 최단 경로): O(V³)

 

 


4️⃣ 예시 문제

💻 10828번 스택: https://www.acmicpc.net/problem/10828

  • list의 append, pop, len, 인덱스를 이용해 stack을 구현
  • stack은 후입선출(LIFO)
# input: 명령의 수 n / n줄에 걸쳐 명령이 하나씩 주어짐
# output: 명령에 맞는 수 출력

import sys

stack = [] # list를 사용해 stack 생성

n = int(sys.stdin.readline()) # 명령의 개수 n 입력 받기

for i in range(n):
    command = sys.stdin.readline().split() # push의 경우 2가지를 입력받아야 하기 때문에 readline으로 받은 뒤 split
    
    if command[0] == 'push':
        stack.append(command[1])
        
    elif command[0] == 'pop':
        if len(stack) != 0: # stack이 emtpy가 아닐 때만 요소 삭제
            temp = stack.pop()
            print(temp)
        else:
            print(-1)
            
    elif command[0] == 'size':
        print(len(stack))
        
    elif command[0] == 'empty':
        if len(stack) == 0: # stack이 empty면
            print(1)
        else:
            print(0)
            
    elif command[0] == 'top':
        if len(stack) != 0: # stack이 emtpy가 아닐 때만 top 출력
            print(stack[-1])
        else:
            print(-1)

 

 

💻 10845번 큐: https://www.acmicpc.net/problem/10845

  • collections.deque를 사용해 양방향으로 데이터를 처리할 수 있는 queue를 사용
  • queue는 선입선출(FIFO)
# input: 명령의 수 n / n개의 명령
# output: 명령에 맞는 수 출력

from collections import deque
import sys

queue = deque() # deque 모듈을 사용해 queue 생성

n = int(sys.stdin.readline())

for i in range(n):
    command = sys.stdin.readline().split()
    
    if command[0] == 'push':
        queue.append(command[1])
        
    elif command[0] == 'pop':
        if len(queue) != 0:
            temp = queue.popleft() # 왼쪽에 있는 요소 pop
            print(temp)
        else:
            print(-1)
            
    elif command[0] == 'size':
        print(len(queue))
        
    elif command[0] == 'empty':
        if len(queue) == 0:
            print(1)
        else:
            print(0)
            
    elif command[0] == 'front':
        if len(queue) != 0:
            print(queue[0]) # 제일 앞에 있는 요소 출력
        else:
            print(-1)
    
    elif command[0] == 'back':
        if len(queue) != 0:
            print(queue[-1]) # 맨 뒤에 있는 요소 출력
        else:
            print(-1)

'Algorithm' 카테고리의 다른 글

[BOJ] 백준 4948 베르트랑 공준 Python  (0) 2025.03.03