-
Notifications
You must be signed in to change notification settings - Fork 2
LIST
LIST는 이중 연결 리스트(doubly-linked list) 컨테이너로, C++ STL의 std::list에 대응됩니다. 각 요소는 독립된 노드에 저장되며, 노드는 앞 노드와 뒤 노드를 가리키는 포인터를 함께 가집니다. 따라서 어느 위치에서든 삽입/삭제가 상수 시간에 수행되지만, 임의 접근(v[i])은 지원하지 않습니다.
LIST는 요소가 메모리에 연속적으로 놓이지 않기 때문에 캐시 지역성 면에서는 VECTOR보다 불리합니다. 대신 반복자가 컨테이너 구조 변경에도 무효화되지 않는다는 특성이 있어, 중간 위치에서의 삽입·삭제가 빈번하거나 외부에서 반복자를 장기 보관해야 하는 시나리오에 적합합니다.
복잡도: 끝(back)/앞(front)에서의 삽입·제거는 상수 시간 O(1), 임의 위치에서의 삽입·제거도 반복자를 이미 알고 있다면 O(1), 탐색(find)과 순회는 선형 시간 O(n)입니다.
| Function | 설명 |
|---|---|
| new_list(T) | T 타입의 새로운 리스트 생성 |
| destroy | 컨테이너 소멸자 |
| Function | 설명 |
|---|---|
| front | 첫 번째 요소에 접근 |
| back | 마지막 요소에 접근 |
| Function | 설명 |
|---|---|
| begin | 시작 반복자를 반환 |
| end | 끝(past-the-end) 반복자를 반환 |
| rbegin | 역방향 시작 반복자를 반환 |
| rend | 역방향 끝 반복자를 반환 |
| next | 반복자를 한 칸 전진 |
| prev | 반복자를 한 칸 후퇴 |
| Function | 설명 |
|---|---|
| size | 요소 개수를 반환 |
| empty | 비었는지 확인 |
| Function | 설명 |
|---|---|
| clear | 모든 요소를 제거 |
| insert | 지정 위치에 요소를 삽입 |
| erase | 지정 범위의 요소를 제거 |
| push_back | 끝에 요소를 추가 |
| pop_back | 마지막 요소를 제거 |
| push_front | 앞에 요소를 추가 |
| pop_front | 첫 번째 요소를 제거 |
| resize | 요소 개수를 n으로 변경 |
| Function | 설명 |
|---|---|
| find | 지정한 요소를 검색 |
| Function | 설명 |
|---|---|
| sort | 리스트를 정렬 (merge sort 기반) |
Note
LIST, new_list
LIST(int) list = new_list(int);요소 타입이 TYPE인 빈 리스트를 생성합니다. 반환값은 TYPE*로, 내부적으로는 이중 연결 리스트의 헤드 노드를 가리키는 불투명 포인터입니다.
| Parameter | 설명 |
|---|---|
| T | 저장할 요소의 타입 (예: int, double, 구조체 등) |
O(1)
LIST(int) list = new_list(int);
LIST(double) dlist = new_list(double);Note
destroy
void destroy(list);리스트의 모든 노드를 순회하면서 각 노드의 메모리를 해제한 뒤, 컨테이너 본체까지 해제하고 포인터를 NULL로 설정합니다.
Warning
VECTOR와 달리 LIST는 각 요소마다 독립된 노드를 동적으로 할당하므로, destroy를 호출하지 않으면 다수의 메모리 누수가 발생합니다.
O(n)
LIST(int) list = new_list(int);
for (int i = 0; i < 10; i++) push_back(list, i);
// list 사용
destroy(list); // 모든 노드 해제Note
front, back
<TYPE> front(list);
<TYPE> back(list);front는 리스트의 첫 번째 요소의 값을, back은 마지막 요소의 값을 반환합니다.
Warning
빈 리스트에서 호출하면 정의되지 않은 동작(undefined behavior)이 발생합니다. 호출 전에 size(list) > 0 또는 !empty(list)를 확인하세요.
O(1)
LIST(int) list = new_list(int);
push_back(list, 10);
push_back(list, 20);
push_back(list, 30);
printf("front: %d\n", front(list)); // 10
printf("back : %d\n", back(list)); // 30Note
begin, end
iterator begin(list);
iterator end(list);begin은 첫 번째 노드를 가리키는 반복자를, end는 past-the-end를 가리키는 반복자를 반환합니다. LIST의 end는 내부적으로 NULL입니다. 리스트가 비어 있으면 begin == end == NULL입니다.
O(1)
LIST(int) list = new_list(int);
for (int i = 0; i < 5; i++) push_back(list, i);
for (int* it = begin(list); it != end(list); it = next(it)) {
printf("[%d] ", *it);
}
// 출력: [0] [1] [2] [3] [4]Note
rbegin, rend
reverse_iterator rbegin(list);
reverse_iterator rend(list);역방향 순회용 반복자를 반환합니다. rbegin은 마지막 노드를, rend는 첫 번째 노드의 이전 위치(내부적으로 NULL)를 가리킵니다.
Warning
역방향 순회 시에는 반드시 prev를 사용해 반복자를 이동시켜야 합니다. VECTOR처럼 it-- 같은 포인터 산술은 노드 포인터에서는 동작하지 않습니다.
O(1)
LIST(int) list = new_list(int);
for (int i = 0; i < 5; i++) push_back(list, i);
for (int* it = rbegin(list); it != rend(list); it = prev(it)) {
printf("[%d] ", *it);
}
// 출력: [4] [3] [2] [1] [0]Note
next, prev
iterator next(it);
iterator prev(it);현재 반복자에서 다음 노드 또는 이전 노드로 이동합니다. LIST의 노드는 불연속적으로 할당되므로 포인터 산술로 이동할 수 없으며, 반드시 이 함수를 사용해야 합니다.
O(1)
LIST(int) list = new_list(int);
push_back(list, 10);
push_back(list, 20);
push_back(list, 30);
int* it = begin(list);
it = next(it);
printf("%d\n", *it); // 20
it = prev(it);
printf("%d\n", *it); // 10Note
size
size_type size(list);리스트가 저장하고 있는 요소의 개수를 반환합니다. 내부적으로 카운터를 유지하므로 상수 시간에 수행됩니다.
O(1)
LIST(int) list = new_list(int);
push_back(list, 1);
push_back(list, 2);
printf("%zu\n", size(list)); // 2Note
empty
bool empty(list);리스트가 비어 있는지 확인합니다. size(list) == 0과 동일하지만 의미가 더 명확합니다.
O(1)
LIST(int) list = new_list(int);
if (empty(list)) {
puts("list is empty");
}
push_back(list, 42);
// empty(list) == falseNote
clear
void clear(list);리스트의 모든 요소를 제거하고 각 노드의 메모리를 해제합니다. 컨테이너 본체는 유지되므로 이후에도 계속 사용할 수 있습니다.
O(n)
LIST(int) list = new_list(int);
for (int i = 0; i < 100; i++) push_back(list, i);
clear(list);
printf("%zu\n", size(list)); // 0Note
insert
void insert(list, iterator pos, <TYPE> value);
void insert(list, iterator pos, size_t n, <TYPE> value);pos가 가리키는 노드의 앞 위치에 값을 삽입합니다. 두 번째 형식은 value를 n번 복사해서 삽입합니다.
| Parameter | 설명 |
|---|---|
| pos | 삽입 위치를 가리키는 반복자 |
| n | 삽입할 요소의 개수 (생략 시 1) |
| value | 삽입할 값 |
Note
VECTOR와 달리 LIST의 insert는 기존 반복자를 무효화하지 않습니다. 리스트에 이미 들고 있던 반복자는 계속 유효합니다.
O(n) — 삽입할 요소 수에 비례
LIST(int) list = new_list(int);
for (int i = 0; i < 5; i++) push_back(list, i);
// [0] [1] [2] [3] [4]
insert(list, begin(list), 777);
// [777] [0] [1] [2] [3] [4]
insert(list, find(list, 3), 5, 999);
// [777] [0] [1] [2] [999] [999] [999] [999] [999] [3] [4]Note
erase
void erase(list, iterator first, iterator last);구간 [first, last)에 해당하는 노드들을 제거하고 메모리를 해제합니다.
Warning
삭제된 노드를 가리키는 반복자는 당연히 무효화됩니다. 다만 삭제 대상이 아닌 다른 노드의 반복자는 여전히 유효합니다.
O(n) — 구간 길이에 비례
LIST(int) list = new_list(int);
for (int i = 0; i < 5; i++) push_back(list, i);
// [0] [1] [2] [3] [4]
erase(list, find(list, 1), find(list, 3));
// [0] [3] [4]Note
push_back
void push_back(list, <TYPE> value);리스트 끝에 값을 추가합니다. 새 노드를 할당해서 tail 뒤에 연결합니다.
O(1)
LIST(int) list = new_list(int);
for (int i = 0; i < 10; i++) {
push_back(list, i);
}Note
pop_back
void pop_back(list);리스트의 마지막 노드를 제거하고 메모리를 해제합니다.
Warning
빈 리스트에서 호출하면 에러가 발생합니다 ("No elements in cstl_list").
O(1)
LIST(int) list = new_list(int);
push_back(list, 1);
push_back(list, 2);
pop_back(list);
printf("%zu\n", size(list)); // 1Note
push_front
void push_front(list, <TYPE> value);리스트 앞에 값을 추가합니다. LIST는 이중 연결이므로 VECTOR와 달리 선두 삽입이 O(1)에 수행됩니다.
O(1)
LIST(int) list = new_list(int);
push_back(list, 2);
push_back(list, 3);
push_front(list, 1);
// [1] [2] [3]Note
pop_front
void pop_front(list);리스트의 첫 번째 노드를 제거하고 메모리를 해제합니다. 선두 제거도 O(1)입니다.
Warning
빈 리스트에서 호출하면 에러가 발생합니다.
O(1)
LIST(int) list = new_list(int);
push_back(list, 1);
push_back(list, 2);
push_back(list, 3);
pop_front(list);
// [2] [3]Note
resize
void resize(list, size_t n);
void resize(list, size_t n, <TYPE> value);리스트의 크기를 n으로 변경합니다.
- 현재 크기보다 작으면 뒤쪽 노드들이 제거됩니다.
- 현재 크기보다 크면
value로 초기화된 노드가 뒤에 추가됩니다.value를 생략하면 0으로 채워집니다.
| Parameter | 설명 |
|---|---|
| n | 변경할 크기 |
| value | 증가분에 채울 값 (선택) |
O(|n - size|)
LIST(int) list = new_list(int);
for (int i = 0; i < 3; i++) push_back(list, i); // [0] [1] [2]
resize(list, 5, 99);
// [0] [1] [2] [99] [99]
resize(list, 2);
// [0] [1]Note
find
iterator find(list, <TYPE> value);리스트를 처음부터 순회하며 value와 memcmp 기준으로 일치하는 첫 번째 노드의 반복자를 반환합니다. 찾지 못하면 NULL을 반환합니다.
Warning
비교는 바이트 단위(memcmp)로 수행됩니다. 구조체에 패딩이 있으면 예기치 않은 결과가 나올 수 있으니 memset으로 초기화하여 사용하세요.
O(n)
LIST(int) list = new_list(int);
for (int i = 0; i < 10; i++) push_back(list, i * i);
int* pos = find(list, 25);
if (pos != NULL) {
printf("found: %d\n", *pos); // 25
}Note
sort
void sort(list);리스트를 오름차순으로 정렬합니다. 내부적으로 merge sort를 사용하므로 LIST의 노드 구조에 최적화되어 있으며, 노드 포인터만 재연결하고 값 자체는 이동하지 않습니다.
Note
연결 리스트에 quicksort를 적용하면 임의 접근이 없어 피벗 선택과 파티셔닝이 비효율적입니다. Merge sort는 연결 리스트에 대해 추가 메모리 없이 O(n log n)을 달성할 수 있는 자연스러운 선택입니다.
O(n log n)
LIST(int) list = new_list(int);
for (int i = 0; i < 50; i++) {
push_back(list, rand32() % 1000);
}
sort(list);
for (int* it = begin(list); it != end(list); it = next(it)) {
printf("[%d]->", *it);
}
puts("[NULL]");main.c의 cstl_list_test02에서 발췌한 종합 예시입니다.
LIST(int) list = new_list(int);
for (int i = 0; i < 10; i++) {
push_back(list, i);
}
// [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
pop_back(list);
// [0] [1] [2] [3] [4] [5] [6] [7] [8]
insert(list, begin(list), 777);
// [777] [0] [1] [2] [3] [4] [5] [6] [7] [8]
insert(list, find(list, 4), 5, 999);
// [777] [0] [1] [2] [3] [999] [999] [999] [999] [999] [4] [5] [6] [7] [8]
erase(list, find(list, 2), find(list, 999));
// [777] [0] [1] [999] [999] [999] [999] [999] [4] [5] [6] [7] [8]
printf("front: %d\n", front(list)); // 777
printf("back : %d\n", back(list)); // 8
sort(list);
for (int* it = begin(list); it != end(list); it = next(it)) {
printf("[%d]->", *it);
}
puts("[NULL]");
destroy(list);| 특성 | VECTOR | LIST |
|---|---|---|
| 메모리 배치 | 연속 (contiguous) | 비연속 (노드 단위 할당) |
임의 접근 v[i]
|
O(1) |
미지원 |
| 끝에 삽입 | amortized O(1)
|
O(1) |
| 앞에 삽입 | O(n) |
O(1) |
| 중간 삽입 (반복자 보유) | O(n) |
O(1) |
| 캐시 지역성 | 우수 | 불리 |
| 반복자 무효화 | 재할당/삽입 시 발생 | 삭제 대상 외에는 유지 |
| 정렬 알고리즘 | pmsort (vector용 merge sort) | sort (list용 merge sort) |
Note
일반적으로는 VECTOR가 기본 선택이 됩니다. LIST는 중간 위치에서 삽입/삭제가 매우 빈번하거나, 외부에서 반복자를 장기 보관해야 하는 경우에 선택하세요.
-
-
Container library
- Sequential Containers
- Associative Containers
- Unordered Containers
- Sorting Algorithm
- Logging library
-
Container library