포스트

[CS 기초 #6] 자료구조: 배열과 연결 리스트 완벽 마스터

[CS 기초 #6] 자료구조: 배열과 연결 리스트 완벽 마스터

가장 기본적인 두 자료구조를 마스터합시다! 배열과 연결 리스트의 차이를 이해하면 다른 모든 자료구조가 쉬워집니다.

🎯 이 글을 읽고 나면

  • 배열과 연결 리스트의 장단점을 비교할 수 있습니다
  • 시간 복잡도를 기준으로 적절한 자료구조를 선택할 수 있습니다
  • 동적 배열의 동작 원리를 이해합니다
  • 이중 연결 리스트를 직접 구현할 수 있습니다
  • 실무에서 어떤 상황에 무엇을 쓸지 판단할 수 있습니다

📚 사전 지식

  • 프로그래밍 기초: 배열, 포인터/참조 개념
  • 메모리 구조: 컴퓨터 구조 편 참고
  • 복잡도 분석: 시간 복잡도 개념 (O(1), O(n) 등)

💡 핵심 개념 미리보기

배열(Array)은 연속된 메모리 공간에 데이터를 저장하여 인덱스로 O(1) 접근이 가능하지만, 중간 삽입/삭제가 비효율적(O(n))입니다. 연결 리스트(Linked List)는 노드들이 포인터로 연결되어 삽입/삭제가 효율적(O(1))이지만, 특정 위치 접근에 O(n)이 필요합니다. 각각의 장단점을 이해하면 상황에 맞는 자료구조를 선택할 수 있습니다!


📚 들어가며: 자료구조는 왜 중요한가?

“프로그램 = 자료구조 + 알고리즘”이라는 유명한 공식이 있습니다. 적절한 자료구조를 선택하면 복잡한 문제도 간단하게 해결할 수 있습니다.

오늘은 가장 기본이 되는 두 자료구조, 배열(Array)연결 리스트(Linked List)를 깊이 있게 알아보겠습니다. 이 둘의 차이를 이해하면 다른 모든 자료구조를 쉽게 이해할 수 있습니다!

🎯 자료구조란?

자료구조는 데이터를 효율적으로 저장하고 관리하는 방법입니다.

graph TD
    DS[자료구조]
    DS --> Linear[선형 구조]
    DS --> NonLinear[비선형 구조]
    
    Linear --> Array[배열]
    Linear --> LinkedList[연결 리스트]
    Linear --> Stack[스택]
    Linear --> Queue[큐]
    
    NonLinear --> Tree[트리]
    NonLinear --> Graph[그래프]
    NonLinear --> Hash[해시 테이블]
    
    style Array fill:#ff9999
    style LinkedList fill:#ff9999

📦 배열 (Array)

배열은 연속된 메모리 공간에 데이터를 순차적으로 저장하는 자료구조입니다.

배열의 메모리 구조

graph LR
    subgraph "메모리 (연속 할당)"
        M0[0x100<br/>10] --> M1[0x104<br/>20]
        M1 --> M2[0x108<br/>30]
        M2 --> M3[0x10C<br/>40]
        M3 --> M4[0x110<br/>50]
    end
    
    subgraph "배열 인덱스"
        I0[arr[0]] -.-> M0
        I1[arr[1]] -.-> M1
        I2[arr[2]] -.-> M2
        I3[arr[3]] -.-> M3
        I4[arr[4]] -.-> M4
    end

배열 구현

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class Array:
    """고정 크기 배열 구현"""
    
    def __init__(self, capacity):
        self.capacity = capacity
        self.data = [None] * capacity
        self.size = 0
    
    def __getitem__(self, index):
        """인덱스로 접근 - O(1)"""
        if 0 <= index < self.size:
            return self.data[index]
        raise IndexError("Index out of range")
    
    def __setitem__(self, index, value):
        """인덱스로 값 설정 - O(1)"""
        if 0 <= index < self.size:
            self.data[index] = value
        else:
            raise IndexError("Index out of range")
    
    def append(self, value):
        """끝에 추가 - O(1)"""
        if self.size >= self.capacity:
            raise OverflowError("Array is full")
        self.data[self.size] = value
        self.size += 1
    
    def insert(self, index, value):
        """중간에 삽입 - O(n)"""
        if self.size >= self.capacity:
            raise OverflowError("Array is full")
        if index < 0 or index > self.size:
            raise IndexError("Index out of range")
        
        # 뒤로 한 칸씩 이동
        for i in range(self.size, index, -1):
            self.data[i] = self.data[i - 1]
        
        self.data[index] = value
        self.size += 1
    
    def remove(self, index):
        """삭제 - O(n)"""
        if index < 0 or index >= self.size:
            raise IndexError("Index out of range")
        
        value = self.data[index]
        
        # 앞으로 한 칸씩 이동
        for i in range(index, self.size - 1):
            self.data[i] = self.data[i + 1]
        
        self.size -= 1
        self.data[self.size] = None
        return value
    
    def search(self, value):
        """선형 검색 - O(n)"""
        for i in range(self.size):
            if self.data[i] == value:
                return i
        return -1
    
    def __str__(self):
        return str([self.data[i] for i in range(self.size)])

# 배열 사용 예시
arr = Array(10)
arr.append(10)
arr.append(20)
arr.append(30)
print(f"배열: {arr}")

arr.insert(1, 15)
print(f"1번 인덱스에 15 삽입: {arr}")

arr.remove(2)
print(f"2번 인덱스 삭제: {arr}")

print(f"값 30의 위치: {arr.search(30)}")

동적 배열 (Dynamic Array)

Python의 list처럼 크기가 자동으로 조절되는 배열입니다.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class DynamicArray:
    """동적 배열 구현 (Python list와 유사)"""
    
    def __init__(self):
        self.capacity = 1
        self.size = 0
        self.data = [None] * self.capacity
    
    def _resize(self, new_capacity):
        """배열 크기 조정 - O(n)"""
        new_data = [None] * new_capacity
        for i in range(self.size):
            new_data[i] = self.data[i]
        self.data = new_data
        self.capacity = new_capacity
        print(f"크기 조정: {self.capacity // 2}{self.capacity}")
    
    def append(self, value):
        """끝에 추가 - Amortized O(1)"""
        if self.size == self.capacity:
            self._resize(self.capacity * 2)  # 2배로 확장
        
        self.data[self.size] = value
        self.size += 1
    
    def pop(self):
        """끝에서 제거 - O(1)"""
        if self.size == 0:
            raise IndexError("Array is empty")
        
        value = self.data[self.size - 1]
        self.size -= 1
        
        # 25% 이하로 줄면 크기 절반으로 축소
        if self.size > 0 and self.size == self.capacity // 4:
            self._resize(self.capacity // 2)
        
        return value
    
    def __len__(self):
        return self.size
    
    def __str__(self):
        return str([self.data[i] for i in range(self.size)])

# 동적 배열 테스트
dynamic_arr = DynamicArray()
print("동적 배열 테스트:")
for i in range(10):
    dynamic_arr.append(i * 10)
    print(f"추가 {i * 10}: 크기={len(dynamic_arr)}, 용량={dynamic_arr.capacity}")

🔗 연결 리스트 (Linked List)

연결 리스트는 노드(Node)들이 포인터로 연결된 자료구조입니다.

연결 리스트의 메모리 구조

graph LR
    subgraph "메모리 (분산 할당)"
        N1[0x200<br/>Data: 10<br/>Next: 0x350] 
        N2[0x350<br/>Data: 20<br/>Next: 0x180]
        N3[0x180<br/>Data: 30<br/>Next: 0x420]
        N4[0x420<br/>Data: 40<br/>Next: NULL]
    end
    
    HEAD[HEAD] --> N1
    N1 --> N2
    N2 --> N3
    N3 --> N4
    N4 --> NULL[NULL]

단일 연결 리스트 구현

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class Node:
    """연결 리스트의 노드"""
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    """단일 연결 리스트"""
    
    def __init__(self):
        self.head = None
        self.size = 0
    
    def prepend(self, data):
        """맨 앞에 추가 - O(1)"""
        new_node = Node(data)
        new_node.next = self.head
        self.head = new_node
        self.size += 1
    
    def append(self, data):
        """맨 뒤에 추가 - O(n)"""
        new_node = Node(data)
        self.size += 1
        
        if not self.head:
            self.head = new_node
            return
        
        current = self.head
        while current.next:
            current = current.next
        current.next = new_node
    
    def insert(self, index, data):
        """특정 위치에 삽입 - O(n)"""
        if index < 0 or index > self.size:
            raise IndexError("Index out of range")
        
        if index == 0:
            self.prepend(data)
            return
        
        new_node = Node(data)
        current = self.head
        
        for _ in range(index - 1):
            current = current.next
        
        new_node.next = current.next
        current.next = new_node
        self.size += 1
    
    def remove(self, data):
        """값으로 삭제 - O(n)"""
        if not self.head:
            return False
        
        if self.head.data == data:
            self.head = self.head.next
            self.size -= 1
            return True
        
        current = self.head
        while current.next:
            if current.next.data == data:
                current.next = current.next.next
                self.size -= 1
                return True
            current = current.next
        
        return False
    
    def search(self, data):
        """검색 - O(n)"""
        current = self.head
        index = 0
        
        while current:
            if current.data == data:
                return index
            current = current.next
            index += 1
        
        return -1
    
    def get(self, index):
        """인덱스로 접근 - O(n)"""
        if index < 0 or index >= self.size:
            raise IndexError("Index out of range")
        
        current = self.head
        for _ in range(index):
            current = current.next
        
        return current.data
    
    def reverse(self):
        """리스트 뒤집기 - O(n)"""
        prev = None
        current = self.head
        
        while current:
            next_node = current.next
            current.next = prev
            prev = current
            current = next_node
        
        self.head = prev
    
    def __str__(self):
        if not self.head:
            return "[]"
        
        result = []
        current = self.head
        while current:
            result.append(str(current.data))
            current = current.next
        
        return " -> ".join(result)

# 연결 리스트 사용 예시
ll = LinkedList()
ll.append(10)
ll.append(20)
ll.append(30)
print(f"연결 리스트: {ll}")

ll.prepend(5)
print(f"맨 앞에 5 추가: {ll}")

ll.insert(2, 15)
print(f"2번 위치에 15 삽입: {ll}")

ll.remove(20)
print(f"20 삭제: {ll}")

ll.reverse()
print(f"뒤집기: {ll}")

이중 연결 리스트 구현

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
class DNode:
    """이중 연결 리스트의 노드"""
    def __init__(self, data):
        self.data = data
        self.prev = None
        self.next = None

class DoublyLinkedList:
    """이중 연결 리스트"""
    
    def __init__(self):
        self.head = None
        self.tail = None
        self.size = 0
    
    def append(self, data):
        """맨 뒤에 추가 - O(1)"""
        new_node = DNode(data)
        
        if not self.head:
            self.head = self.tail = new_node
        else:
            new_node.prev = self.tail
            self.tail.next = new_node
            self.tail = new_node
        
        self.size += 1
    
    def prepend(self, data):
        """맨 앞에 추가 - O(1)"""
        new_node = DNode(data)
        
        if not self.head:
            self.head = self.tail = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node
        
        self.size += 1
    
    def remove_at(self, index):
        """인덱스로 삭제 - O(n)"""
        if index < 0 or index >= self.size:
            raise IndexError("Index out of range")
        
        # 앞쪽에서 접근이 빠른지 뒤쪽에서 접근이 빠른지 판단
        if index < self.size // 2:
            current = self.head
            for _ in range(index):
                current = current.next
        else:
            current = self.tail
            for _ in range(self.size - 1 - index):
                current = current.prev
        
        # 노드 제거
        if current.prev:
            current.prev.next = current.next
        else:
            self.head = current.next
        
        if current.next:
            current.next.prev = current.prev
        else:
            self.tail = current.prev
        
        self.size -= 1
        return current.data
    
    def __str__(self):
        if not self.head:
            return "[]"
        
        result = []
        current = self.head
        while current:
            result.append(str(current.data))
            current = current.next
        
        return "".join(result)
    
    def reverse_str(self):
        """역방향 출력"""
        if not self.tail:
            return "[]"
        
        result = []
        current = self.tail
        while current:
            result.append(str(current.data))
            current = current.prev
        
        return "".join(result)

# 이중 연결 리스트 테스트
dll = DoublyLinkedList()
dll.append(10)
dll.append(20)
dll.append(30)
dll.prepend(5)
print(f"이중 연결 리스트: {dll}")
print(f"역방향: {dll.reverse_str()}")

dll.remove_at(2)
print(f"인덱스 2 삭제 후: {dll}")

⚖️ 배열 vs 연결 리스트 비교

시간 복잡도 비교

연산 배열 연결 리스트
접근 (Access) O(1) O(n)
검색 (Search) O(n) O(n)
삽입 (맨 앞) O(n) O(1)
삽입 (맨 뒤) O(1)* O(n)**
삽입 (중간) O(n) O(n)
삭제 (맨 앞) O(n) O(1)
삭제 (맨 뒤) O(1) O(n)**
삭제 (중간) O(n) O(n)

*동적 배열의 경우 Amortized O(1) **이중 연결 리스트에서 tail 포인터가 있으면 O(1)

공간 복잡도 비교

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
import sys

def memory_comparison():
    """메모리 사용량 비교"""
    
    # 배열 (Python list)
    arr = [i for i in range(1000)]
    arr_memory = sys.getsizeof(arr)
    
    # 연결 리스트
    ll = LinkedList()
    for i in range(1000):
        ll.append(i)
    
    # 노드 하나의 크기 추정
    node = Node(0)
    node_size = sys.getsizeof(node) + sys.getsizeof(node.data) + sys.getsizeof(node.next)
    ll_memory = node_size * 1000
    
    print("1000개 정수 저장 시 메모리 사용량:")
    print(f"배열: {arr_memory:,} bytes")
    print(f"연결 리스트: {ll_memory:,} bytes (추정)")
    print(f"오버헤드: {(ll_memory / arr_memory - 1) * 100:.1f}%")

memory_comparison()

🎯 언제 무엇을 사용해야 할까?

배열을 사용해야 할 때

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class ArrayUseCases:
    """배열이 적합한 경우"""
    
    @staticmethod
    def random_access_example():
        """빈번한 인덱스 접근이 필요한 경우"""
        # 이미지 픽셀 처리
        image = [[0] * 100 for _ in range(100)]
        
        # O(1) 접근으로 특정 픽셀 수정
        image[50][50] = 255
        
        # 행렬 연산
        matrix = [[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]]
        
        # O(1) 접근으로 대각선 합
        diagonal_sum = sum(matrix[i][i] for i in range(3))
        print(f"대각선 합: {diagonal_sum}")
    
    @staticmethod
    def cache_friendly_example():
        """캐시 지역성이 중요한 경우"""
        # 연속된 메모리 접근으로 캐시 효율 극대화
        data = list(range(1000000))
        
        # 순차 접근 - 캐시 친화적
        total = 0
        for value in data:
            total += value
        
        return total
    
    @staticmethod
    def binary_search_example():
        """이진 탐색이 필요한 경우"""
        sorted_array = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
        
        def binary_search(arr, target):
            left, right = 0, len(arr) - 1
            
            while left <= right:
                mid = (left + right) // 2
                if arr[mid] == target:
                    return mid
                elif arr[mid] < target:
                    left = mid + 1
                else:
                    right = mid - 1
            
            return -1
        
        index = binary_search(sorted_array, 11)
        print(f"11의 위치: {index}")

연결 리스트를 사용해야 할 때

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class LinkedListUseCases:
    """연결 리스트가 적합한 경우"""
    
    @staticmethod
    def frequent_insertion_deletion():
        """빈번한 삽입/삭제가 발생하는 경우"""
        # LRU 캐시 구현
        class LRUCache:
            def __init__(self, capacity):
                self.capacity = capacity
                self.cache = DoublyLinkedList()
                self.map = {}
            
            def get(self, key):
                if key in self.map:
                    # 노드를 맨 앞으로 이동 (최근 사용)
                    # 실제로는 노드 참조를 저장하여 O(1)로 구현
                    return self.map[key]
                return -1
            
            def put(self, key, value):
                if len(self.map) >= self.capacity:
                    # 가장 오래된 항목 제거 (tail)
                    pass
                # 새 항목을 맨 앞에 추가 (head)
                self.cache.prepend(value)
                self.map[key] = value
    
    @staticmethod
    def undo_redo_system():
        """실행 취소/재실행 시스템"""
        class History:
            def __init__(self):
                self.history = DoublyLinkedList()
                self.current = None
            
            def add_action(self, action):
                """새 작업 추가"""
                self.history.append(action)
            
            def undo(self):
                """이전 상태로"""
                # 이중 연결 리스트로 쉽게 구현
                pass
            
            def redo(self):
                """다음 상태로"""
                # 이중 연결 리스트로 쉽게 구현
                pass
    
    @staticmethod
    def music_playlist():
        """음악 재생 목록"""
        class Playlist:
            def __init__(self):
                self.songs = DoublyLinkedList()
                self.current = None
            
            def add_song(self, song):
                self.songs.append(song)
            
            def next_song(self):
                # 다음 곡으로
                if self.current and self.current.next:
                    self.current = self.current.next
                    return self.current.data
            
            def prev_song(self):
                # 이전 곡으로
                if self.current and self.current.prev:
                    self.current = self.current.prev
                    return self.current.data

🔄 순환 연결 리스트

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class CircularLinkedList:
    """순환 연결 리스트"""
    
    def __init__(self):
        self.head = None
        self.size = 0
    
    def append(self, data):
        """추가 - O(n)"""
        new_node = Node(data)
        
        if not self.head:
            self.head = new_node
            new_node.next = self.head  # 자기 자신을 가리킴
        else:
            current = self.head
            while current.next != self.head:
                current = current.next
            current.next = new_node
            new_node.next = self.head
        
        self.size += 1
    
    def rotate(self, k):
        """k만큼 회전 - O(k)"""
        if not self.head or k == 0:
            return
        
        for _ in range(k % self.size):
            current = self.head
            while current.next != self.head:
                current = current.next
            
            # head를 다음 노드로 이동
            self.head = self.head.next
    
    def josephus_problem(self, k):
        """요세푸스 문제 해결"""
        if not self.head:
            return None
        
        current = self.head
        
        while self.size > 1:
            # k-1번 이동
            for _ in range(k - 1):
                prev = current
                current = current.next
            
            # current 노드 제거
            print(f"제거: {current.data}")
            prev.next = current.next
            current = current.next
            self.size -= 1
        
        return current.data

# 순환 연결 리스트 활용 예제
print("라운드 로빈 스케줄링 시뮬레이션:")
scheduler = CircularLinkedList()
for i in range(1, 6):
    scheduler.append(f"Process {i}")

# 요세푸스 문제
print("\n요세푸스 문제 (k=3):")
josephus = CircularLinkedList()
for i in range(1, 8):
    josephus.append(i)
survivor = josephus.josephus_problem(3)
print(f"생존자: {survivor}")

💡 실전 문제 풀이

문제 1: 두 정렬된 연결 리스트 병합

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def merge_sorted_lists(l1, l2):
    """두 정렬된 연결 리스트를 병합"""
    dummy = Node(0)
    current = dummy
    
    while l1 and l2:
        if l1.data <= l2.data:
            current.next = l1
            l1 = l1.next
        else:
            current.next = l2
            l2 = l2.next
        current = current.next
    
    # 남은 노드 연결
    current.next = l1 if l1 else l2
    
    return dummy.next

문제 2: 연결 리스트의 사이클 감지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def has_cycle(head):
    """플로이드의 토끼와 거북이 알고리즘"""
    if not head or not head.next:
        return False
    
    slow = head
    fast = head.next
    
    while fast and fast.next:
        if slow == fast:
            return True
        slow = slow.next
        fast = fast.next.next
    
    return False

문제 3: 배열에서 k번째 큰 수 찾기

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
31
32
def find_kth_largest(arr, k):
    """Quick Select 알고리즘 - 평균 O(n)"""
    def partition(left, right):
        pivot = arr[right]
        i = left - 1
        
        for j in range(left, right):
            if arr[j] >= pivot:
                i += 1
                arr[i], arr[j] = arr[j], arr[i]
        
        arr[i + 1], arr[right] = arr[right], arr[i + 1]
        return i + 1
    
    def quick_select(left, right, k):
        if left == right:
            return arr[left]
        
        pivot_index = partition(left, right)
        
        if pivot_index == k - 1:
            return arr[pivot_index]
        elif pivot_index > k - 1:
            return quick_select(left, pivot_index - 1, k)
        else:
            return quick_select(pivot_index + 1, right, k)
    
    return quick_select(0, len(arr) - 1, k)

# 테스트
arr = [3, 2, 1, 5, 6, 4]
print(f"2번째로 큰 수: {find_kth_largest(arr.copy(), 2)}")

🎯 핵심 정리

꼭 기억해야 할 5가지

  1. 배열은 인덱스 접근이 O(1), 연결 리스트는 O(n)
  2. 배열은 연속 메모리, 연결 리스트는 분산 메모리
  3. 삽입/삭제는 연결 리스트가 유리 (특히 맨 앞)
  4. 캐시 효율은 배열이 우수
  5. 메모리 오버헤드는 연결 리스트가 더 큼

실무 활용 팁

  • 크기가 고정적이고 접근이 빈번하면 → 배열
  • 크기가 동적이고 삽입/삭제가 빈번하면 → 연결 리스트
  • 대부분의 경우 동적 배열(Python list)이 적절
  • 특수한 요구사항이 있을 때만 연결 리스트 고려

📚 추가 학습 자료

추천 문제

  • LeetCode: Reverse Linked List, Merge Two Sorted Lists
  • 프로그래머스: 주식가격, 기능개발
  • 백준: 1406번(에디터), 1158번(요세푸스)

심화 주제

  • Skip List (확률적 자료구조)
  • XOR Linked List (메모리 절약)
  • Unrolled Linked List (캐시 친화적)

🚀 다음 시간 예고

다음 포스트에서는 스택(Stack)과 큐(Queue)를 다룹니다. LIFO와 FIFO의 원리, 그리고 실제 활용 사례를 알아보겠습니다!

✅ 이 글에서 배운 것

스스로 확인해보세요! 각 항목을 설명할 수 있다면 체크하세요.

개념 이해

  • 배열과 연결 리스트의 메모리 구조 차이
  • 각 자료구조의 시간 복잡도 (접근, 삽입, 삭제)
  • 동적 배열의 크기 조정 원리
  • 단일/이중 연결 리스트의 차이
  • 순환 연결 리스트의 특징

실용적 이해

  • 언제 배열을, 언제 연결 리스트를 사용해야 하는지
  • Python list와 Java ArrayList의 내부 동작
  • 캐시 친화성이 배열에 유리한 이유
  • 연결 리스트의 메모리 오버헤드

실습 완료

  • 동적 배열 구현 코드를 이해했다
  • 단일/이중 연결 리스트를 직접 구현해봤다
  • 각 자료구조의 연산 성능을 비교해봤다
  • 실제 활용 예제 (LRU 캐시 등)를 실행했다

📌 시리즈 네비게이션

순서 제목 링크
5 네트워크 기초: OSI 7계층, TCP/IP 이해하기 ← 이전 글
6 배열과 연결 리스트 현재 글
7 스택과 큐: LIFO와 FIFO의 활용 다음 글 →
📋 전체 시리즈 목차 보기

기초 이론편

  1. CS 입문 ✅
  2. 컴퓨터 구조 ✅
  3. 이진수와 논리 게이트 ✅
  4. 운영체제 기초 ✅
  5. 네트워크 기초 ✅

자료구조편

  1. 배열과 연결 리스트 ✅
  2. 스택과 큐
  3. 트리 구조
  4. 그래프
  5. 해시 테이블

알고리즘편

  1. 복잡도 분석
  2. 정렬 알고리즘
  3. 탐색 알고리즘
  4. 동적 계획법
  5. 그리디와 분할정복

실무 응용편

  1. 데이터베이스 이론
  2. 소프트웨어 공학
  3. 보안 기초
  4. 분산 시스템
  5. CS 종합과 면접 준비
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.