배열 vs 링크드 리스트
기술노트
배열 vs 링크드 리스트
배열(Array)
- 정의: 연속된 메모리 공간에 순차적으로 데이터를 저장하는 자료구조
- 메모리 구조: 연속적인 메모리 할당, 인덱스를 통한 직접 접근 가능
- 크기: 대부분의 언어에서 고정된 크기로 선언 (일부 언어는 동적 배열 지원)
배열의 특징
- 인덱싱: O(1) 시간에 임의의 위치 접근 가능
- 메모리 효율: 순차적 메모리 할당으로 캐시 지역성 우수
- 추가/삭제: 요소 추가/삭제 시 다른 요소들의 이동 필요 (O(n) 시간 복잡도)
- 크기 제한: 선언 시 크기 고정 (동적 배열은 재할당 통해 확장)
배열의 시간 복잡도
- 접근: O(1) - 인덱스를 통한 직접 접근
- 검색: O(n) - 정렬되지 않은 배열에서 순차 검색
- 삽입: O(n) - 최악의 경우 (맨 앞에 삽입)
- 삭제: O(n) - 최악의 경우 (맨 앞에서 삭제)
배열 구현 예시 (Java)
// 정적 배열 선언
int[] staticArray = new int[5];
// 배열 요소 접근
staticArray[0] = 10;
int value = staticArray[0];
// 동적 배열 (ArrayList)
import java.util.ArrayList;
ArrayList<Integer> dynamicArray = new ArrayList<>();
dynamicArray.add(10); // 요소 추가
int element = dynamicArray.get(0); // 요소 접근
dynamicArray.remove(0); // 요소 삭제
링크드 리스트(Linked List)
- 정의: 각 노드가 데이터와 다음 노드를 가리키는 포인터로 구성된 자료구조
- 메모리 구조: 비연속적 메모리 할당, 포인터를 통한 노드 연결
- 크기: 동적으로 크기가 변경 가능
링크드 리스트의 특징
- 메모리 할당: 필요할 때마다 노드 동적 할당
- 삽입/삭제 효율: 포인터만 변경하면 되므로 O(1) 시간에 가능 (위치를 알고 있을 경우)
- 순차 접근: 특정 위치 접근을 위해 처음부터 순차적으로 탐색 필요
- 추가 메모리: 데이터 외에 포인터 저장을 위한 추가 메모리 필요
링크드 리스트의 시간 복잡도
- 접근: O(n) - 순차적 접근만 가능
- 검색: O(n) - 처음부터 순차 검색
- 삽입: O(1) - 위치를 알고 있을 경우 (헤드/테일)
- 삭제: O(1) - 위치를 알고 있을 경우 (헤드/테일)
링크드 리스트 종류
- 단일 연결 리스트 (Singly Linked List)
- 각 노드가 데이터와 다음 노드 포인터 포함
- 한 방향으로만 순회 가능
- 이중 연결 리스트 (Doubly Linked List)
- 각 노드가 데이터, 이전 노드 포인터, 다음 노드 포인터 포함
- 양방향 순회 가능
- 추가 메모리 사용
- 원형 연결 리스트 (Circular Linked List)
- 마지막 노드가 첫 번째 노드를 가리킴
- 끝없는 순회 가능
링크드 리스트 구현 예시 (Java)
// 단일 연결 리스트 노드 클래스
class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
this.next = null;
}
}
// 단일 연결 리스트 클래스
class LinkedList {
Node head;
// 노드 추가
public void append(int data) {
Node newNode = new Node(data);
if (head == null) {
head = newNode;
return;
}
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
// 노드 출력
public void printList() {
Node current = head;
while (current != null) {
System.out.print(current.data + " -> ");
current = current.next;
}
System.out.println("null");
}
}
// 사용 예시
public class LinkedListExample {
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);
list.printList(); // 출력: 1 -> 2 -> 3 -> null
}
}
배열과 링크드 리스트 비교
특성 | 배열 | 링크드 리스트 |
---|---|---|
메모리 할당 | 연속적 | 비연속적 |
요소 접근 | O(1) - 직접 접근 | O(n) - 순차 접근 |
메모리 효율 | 데이터만 저장 | 데이터 + 포인터 저장 |
삽입/삭제 효율 | O(n) | O(1) - 위치 알고 있을 경우 |
크기 변경 | 고정 크기 (동적 배열은 재할당) | 동적 크기 변경 용이 |
캐시 지역성 | 우수 | 낮음 |
구현 복잡도 | 간단 | 상대적으로 복잡 |
사용 시나리오
배열이 적합한 경우
- 요소에 대한 빠른 접근이 필요할 때
- 데이터 크기가 미리 알려져 있을 때
- 메모리 효율성이 중요할 때
- 캐시 성능이 중요한 경우
- 간단한 자료구조가 필요할 때
링크드 리스트가 적합한 경우
- 데이터의 삽입/삭제가 빈번할 때
- 데이터 크기를 미리 알 수 없거나 자주 변경될 때
- 메모리가 제한적이지 않을 때
- 중간 위치에서의 삽입/삭제가 많은 경우
- 동적 메모리 할당이 필요한 경우
실제 응용 사례
- 배열:
- 이미지 처리 (픽셀 데이터)
- 행렬 연산
- 해시 테이블 구현
- 정렬 알고리즘
- 링크드 리스트:
- 스택 및 큐 구현
- 메모리 관리 (가용 메모리 블록 관리)
- 다항식 표현
- 그래프의 인접 리스트 표현
- LRU 캐시 구현