연결리스트
일반적으로 연결 리스트는 구조체와 포인터를 함께 사용하여 구현합니다
리스트의 중간 지점에 노드를 추가하거나 삭제할 수 있어야 합니다
포인터 기반의 연결리스트는 필요할 때마다 메모리 공간을 할당 받습니다
연결 리스트의 필요성
일반적으로 배열을 사용하여 데이터를 순차적으로 저장하고,나열할 수 있습니다
배열을 사용하는 경우 메모리 공간이 불필요하게 낭비 될 수 있습니다
배열 기반의 리스트 예시
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
|
#include <stdio.h>
#define INF 10000
int arr[INF];
int count = 0;
void addBack(int data) {
arr[count] = data;
count++;
}
void addFirst(int data) {
for (int i = count; i >= 1; i--) {
arr[i] = arr[i - 1];
}
arr[0] = data;
count++;
}
void removeAt(int index) {
for (int i = index; i < count - 1; i++) {
arr[i] = arr[i + 1];
}
count--;
}
void show() {
for (int i = 0; i < count; i++) {
printf("%d ", arr[i]);
}
}
int main(void) {
addBack(5);
addBack(8);
addBack(7);
addFirst(3);
addFirst(4);
show();
}
|
cs |
배열 기반 리스트의 특징
배열로 만들었으므로 특정한 위치의 원소에 즉시 접근할 수 있다는 장점이 있습니다
데이터가 들어갈 공간을 미리 메모리에 할당해야 한다는 단점이 있습니다
원하는 위치로의 삽입이나 삭제가 비효율적입니다
단일 연결 리스트
하나의 구조체 안에 두 개의 변수가 들어가게 만듭니다
변수 하나는 데이터를 담고 다른 하나는 다음 구조체를 가리키는 변수입니다
next | data | next | NULL | |
Head | 일반 노드 |
단일 연결 리스트는 위와 같은 형태로 나타낼 수 있습니다
포인터를 이용해 단방향적으로 다음 노드를 가리킵니다
일반적으로 연결리스트의 시작 노드를 헤드(Head)라고 하며 별도로 관리합니다
끝 노드의 다음 위치 값으로는 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
|
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int data;
struct Node* next;
} Node;
Node* head;
int main(void) {
head = (Node*)malloc(sizeof(Node));
Node* node1 = (Node*)malloc(sizeof(Node));
node1->data = 1;
Node *node2 = (Node*)malloc(sizeof(Node));
node2->data = 2;
head->next = node1;
node1->next = node2;
node2->next = NULL;
Node* cur = head->next;
while (cur != NULL) {
printf("%d ", cur->data);
cur = cur->next;
}
return 0;
}
|
cs |
연결 리스트 삽입 함수
1
2
3
4
5
6
|
void addFront(Node* root, int data) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = root->next;
root->next = node;
}
|
cs |
연결 리스트 삭제 함수
1
2
3
4
5
|
void removeFront(Node* root) {
Node* front = root->next;
root->next = front->next;
free(front);
}
|
cs |
연결 리스트 메모리 해제 함수
1
2
3
4
5
6
7
8
|
void freeAll(Node* root) {
Node* cur = head->next;
while (cur != NULL) {
Node* next = cur->next;
free(cur);
cur = next;
}
}
|
cs |
연결 리스트 전체 출력 함수
1
2
3
4
5
6
7
|
void showAll(Node* root) {
Node* cur = head->next;
while (cur != NULL) {
pritnf("%d ", cur->data);
cur = cur->next;
}
}
|
cs |
완성된 연결 리스트 사용하는 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int main(void) {
head = (Node*)malloc(sizeof(Node));
head->next = NULL;
addFront(head, 2);
addFront(head, 1);
addFront(head, 7);
addFront(head, 9);
addFront(head, 8);
removeFront(head);
showAll(head);
freeAll(head);
return 0;
}
|
cs |
연결 리스트 구현에 있어서 주의할 점
위 소스 코드들에 덧붙여서 삽입 및 삭제 기능에서의 예외 사항을 처리할 필요가 있습니다
(삭제할 원소가 없는데 삭제하는 경우, Head 노드 자체를 잘못 넣은 경우 등을 체크해야합니다)
연결 리스트의 특징
삽입과 삭제가 배열 기반 리스트의 비해서 간단합니다
배열 기반 리스트와 다르게 특정 인덱스로 즉시 접근하지 못하며, 원소를 차례대로 검색해야 합니다
추가적인 포인터 변수가 사용되므로 메모리 공간이 낭비됩니다
삽입과 삭제가 많은 경우에서 효율적이지만
특정한 인덱스에 바로 참조해야 할 때가 많다면 배열을 이용하는 것이 효율적입니다
'자료구조와 알고리즘' 카테고리의 다른 글
스택 (0) | 2021.02.27 |
---|---|
양방향 연결 리스트 (0) | 2021.02.27 |
자료구조의 개요 (0) | 2021.02.16 |