[바킹독]4. 연결 리스트
https://youtu.be/C6MX5u7r72E?si=z4qj8E5HXtGTojKZ
[실전 알고리즘] 0x04강 - 연결 리스트
안녕하세요, 바킹독이에요. 배열은 복습 잘 하셨나요? 이번 시간에는 연결 리스트라는 것을 같이 배워보겠습니다. 배열에서 한 것 처럼 연결 리스트가 무엇인지 알아보고, 같이 구현해볼 것입니
blog.encrypted.gg
1. 정의와 성질
2. 기능과 구현
3. STL list
4. 연습 문제
1. 정의
연결 리스트 : 원소들을 저장할 때 그 다음 원소가 있는 위치를 포함시키는 방식으로 저장하는 자료구조
원소들은 이곳 저곳에 흩어져있음
2. 성질
1) k번째 원소를 확인/변경하기 위해 O(k)가 필요
2) 임의의 위치에 원소를 추가/임의 위치의 원소 제거 : O(1)
3) 원소들이 메모리 상에 연속해있지 않아 Cache hit rate가 낮지만 할당이 다소 쉬움
3. 연결 리스트의 종류
단일 연결 리스트 : 각 원소가 자신의 다음 원소의 주소를 들고 있는 연결 리스트
이중 연결 리스트 : 각 원소가 자신의 이전 원소와 다음 원소의 주소를 둘 다 들고 있음
단일 연결 리스트에서는 주어진 원소의 이전 원소가 무엇인지를 알 수 없는데 이중 연결 리스트에서는 알 수 있음
단점 : 원소가 가지고 있어야 하는 정보가 1개 더 추가되니 메모리를 더 씀
STL 연결 리스트 : list / 이중 연결 리스트 (구조)
원형 연결 리스트 : 끝이 처음과 연결되어 있음
각 원소가 이전과 다음 원소의 주소를 모두 들고 있어도 상관 x
4. 배열 vs. 연결 리스트
배열 | 연결 리스트 | |
k번째 원소의 접근 | O(1) | O(k) |
임의 위치에 원소 추가/제거 | O(N) | O(1) |
메모리 상의 배치 | 연속 | 불연속 |
추가적으로 필요한 공간 (Overhead) | - | O(N) |
5. 기능
1) 임의의 위치에 있는 원소를 확인/변경 : O(N)
2) 임이의 위치에 원소를 추가/임의 위치의 원소 제거 : O(1)
6. 구현
1) Node 구조체
struct NODE {
struct NODE *prev, *next;
int data;
}
2) 배열 (야매 연결 리스트)
const int MX = 1000005;
int dat[MX], pre[MX], nxt[MX];
int unused = 1;
fill(pre, pre+MX, -1);
fill(nxt, nxt+MX, -1);
3) traverse 함수
const int MX = 1000005;
int dat[MX], pre[MX], nxt[MX];
int unused = 1;
void traverse() {
int cur = nxt[0];
while(cur != -1) {
cout << dat[cur] << '';
cur = nxt[cur];
}
cout << "\n\n";
}
4) insert 함수
void insert(int addr, int num) {
dat[unused] = num;
pre[unused] = addr;
nxt[unused] = nxt[addr];
if(nxt[addr] != -1)
pre[nxt[addr]] = unused;
nxt[addr] = unused;
unused++;
}
1. 새로운 원소를 생성
2. 새 원소의 pre 값에 삽입할 위치의 주소를 대입
3. 새 원소의 nxt 값에 삽입할 위치의 nxt 값을 대입
4. 삽입할 위치의 nxt 값과 삽입할 위치의 다음 원소의 pre 값을 새 원소로 변경
5. unused 1 증가
5) erase 함수
void erase(int addr) {
nxt[pre[addr]] = nxt[addr];
if(nxt[addr] != -1)
pre[nxt[addr]] = pre[addr];
}
7. STL list
int main(void) {
list<int> L = {1,2}; // 1 2
list<int>::iterator t = L.begin(); // t는 1을 가리키는 중
L.push_front(10); // 10 1 2
cout << *t << '\n'; // t가 가리키는 값 = 1을 출력
L.push_back(5); // 10 1 2 5
L.insert(t, 6); // t가 가리키는 곳 앞에 6을 삽입, 10 6 1 2 5
t++; // t를 1칸 앞으로 전진, 현재 t가 가리키는 값은 2
t = L.erase(t); // t가 가리키는 값을 제거, 그 다음 원소인 5의 위치를 반환
// 10 6 1 5, t가 가리키는 값은 5
cout << *t << '\n'; // 5
for(auto i : L) cout << i << ' ';
cout << '\n';
for(list<int>::iterator it = L.begin(); it != L.end(); it++)
cout << *it << ' ';
}
iterator(반복자) : 컨테이너의 요소를 가리키고 순회하는 데 사용되는 객체, 포인터와 유사한 개념, 컨테이너의 요소에 접근하고 조작할 수 있게 함
- *it : 역참조
- 컨테이너 관련 메소드
- L.begin(): 리스트 L의 첫 번째 요소를 가리키는 반복자를 반환.
- L.end(): 리스트 L의 마지막 요소 다음을 가리키는 반복자를 반환. 실제 마지막 요소가 아닌, 그 다음 위치를 가리킴
- 특수 목적 반복자
- back_inserter(container): 컨테이너 끝에 요소 추가
- front_inserter(container): 컨테이너 앞에 요소 추가
- inserter(container, it): 지정된 위치에 요소 삽입
8. 연습문제
1) BOJ 에디터 (1406)
2) 손코딩 문제 1
3) 손코딩 문제 2
4) 손코딩 문제 3