연결 리스트

덤프버전 :

'''이론 컴퓨터 과학
{{{#fff

Theoretical Computer Science
'''

[ 펼치기 · 접기 ]
이론
기본 대상
수학기초론(수리논리학(논리 연산) · 계산 가능성 이론 · 범주론 · 집합론) · 이산수학(그래프 이론) · 수치해석학 · 확률론통계학] · 선형대수학
다루는 대상과 주요 토픽
계산 가능성 이론
재귀함수 · 튜링 기계 · 람다 대수 · 처치-튜링 명제 · 바쁜 비버
오토마타 이론
FSM · 푸시다운 · 튜링 머신(폰노이만 구조) · 정규 표현식 · 콘웨이의 생명 게임
계산 복잡도 이론
점근 표기법 · 튜링 기계^고전, PRAM, 양자, 비결정론적^ · 알고리즘 · 자료구조 · 알고리즘 패러다임(그리디 알고리즘, 동적 계획법)
수학적 최적화
조합 최적화
외판원 순회 문제 · 담금질 기법 · 유전 알고리즘 · 기계학습
볼록 최적화
내부점 방법 · 경사하강법
선형계획법
심플렉스법
정보이론
데이터 압축(무손실 압축 포맷 · 손실 압축 포맷) · 채널 코딩(채널 용량) · 알고리즘 정보 이론(AIT) · 양자정보과학
컴퓨팅 방법론
병렬 컴퓨팅(병렬 아키텍처 · 암달의 법칙 · 병렬 알고리즘) · 분산 컴퓨팅(분산 알고리즘 · 클러스터 컴퓨팅 · 그리드 컴퓨팅 · 클라우드 컴퓨팅) · 멀티코어 컴퓨팅 · 대칭형 다중 처리(SMP)
암호학
해시(MD5 · 암호화폐 · 사전 공격(레인보우 테이블) · SHA) · 양자 암호
대칭키 암호화 방식
블록 암호 알고리즘(AES · ARIA · LEA · Camellia) · 스트림 암호 알고리즘(RC4)
공개키 암호화 방식
공개키 암호 알고리즘(타원 곡선 암호 · RSA) · 신원 기반 암호 알고리즘(SM9)
프로그래밍 언어이론
프로그래밍 언어(함수형 언어 · 객체 지향 프로그래밍) · 메타 프로그래밍 · 형식언어 · 유형 이론 · 프로그래밍 언어 의미론 · 컴파일러 이론
주요 알고리즘 및 자료구조
기초
정렬 알고리즘 · 순서도 · 탐색 알고리즘
추상적 자료형 및 구현
배열^벡터^ · 리스트^연결 리스트^ · 셋(set)^레드-블랙 트리, B-트리^ · 우선순위 큐^, 피보나치 힙^
계산 수론 및 암호학
밀러-라빈 소수판별법 · Pollard-rho 알고리즘 · 쇼어 알고리즘 · LLL 알고리즘
계산기하학
볼록 껍질 · 들로네 삼각분할 및 보로노이 도형^Fortune의 line-sweeping 알고리즘^ · 범위 탐색^vp-tree, R-tree^ · k-NN
그래프 이론
탐색^BFS, DFS, 다익스트라 알고리즘, A* 알고리즘^ · 에드몬드-카프 · 크루스칼 알고리즘 · 위상 정렬 · 네트워크 이론
정리
정지 문제대각선 논법 · 암달의 법칙 · P-NP 문제미해결 · 콜라츠 추측미해결
틀:이산수학 · 틀:수학기초론 · 틀:컴퓨터공학




1. 개요
2. 설명
3. 구현 방법
3.1. 단순 연결 리스트 (Singly Linked List)
3.2. 이중 연결 리스트 (Doubly Linked List)
3.3. 순환 연결 리스트 (Circular linked list)
3.4. 청크 리스트(Chunked List)
4. 분석
5. C에서의 활용
6. C++에서의 활용
7. LISP와 연결 리스트


1. 개요[편집]


Linked List

추상적 자료형인 리스트를 구현한 자료구조로, Linked List라는 말 그대로 어떤 데이터 덩어리(이하 노드Node)를 저장할 때 그 다음 순서의 자료가 있는 위치를 데이터에 포함시키는 방식으로 자료를 저장한다. 예를 들어 한 반에 있는 학생들의 자료를 저장한다면, 학생 하나하나의 신상명세 자료를 노드로 만들고, 1번 학생의 신상명세 자료에 2번 학생 신상명세가 어디있는지 표시를 해 놓는 방식이다. 쉽게 생각하면 자료를 비엔나 소시지마냥 줄줄이 엮어놓은 것이다.

2. 설명[편집]


파일:3-8-1-1.png

배열철수 1번, 영희 2번, 민수 3번, 순이 4번...식으로 자료의 순번을 맞춘다면, 연결리스트는 철수 다음 영희, 영희 다음 민수, 민수 다음 순이....식으로 자료를 연결해놓는다. 따라서 배열과는 달리 새로운 데이터, 즉 노드를 뒤에 연결하거나 중간에 노드 몇개를 껴놓는 것이 쉽다.[1] 그러나 배열에서는 자료마다 번호가 있어서 특정한 자료를 불러내기가 편리한 반면, 연결리스트는 자료 번호가 없이 그저 연결 관계만 있기 때문에 특정한 노드를 불러내기 어려운 단점이 있다.[2][3]

연결 리스트는 배열에 비해 데이터의 추가/삽입 및 삭제가 용이하나 노드를 하나하나 순차적으로 탐색하지 않으면 특정 위치의 요소에 접근할 수 없어 일반적으로 탐색 속도가 떨어진다. 즉 탐색 또는 정렬을 자주 하면 배열을, 추가/삭제가 많으면 연결 리스트를 사용하는 것이 유리하다. 단, 연결 리스트라고 해서 반드시 순차 탐색만 해야 한다는 법은 없다. B+tree 자료구조는 연결 리스트에 힙을 더한 것 같은 모양새를 갖고 있는데 이 자료구조는 데이터의 추가/삭제/정렬/조회 모두에 유리하다. 단점은 그 구현의 복잡도. 물론 여러분이 직접 구현할 필요는 전혀 없다. 데이터베이스와 파일시스템이 이 B-tree의 구현체니까 그렇다.

또한 배열은 크기를 크게 키우기가 어려운 단점이 있다. 배열은 연속된 메모리 공간을 할당받아야 하는데 크기가 커지면 그만한 '연속된' 공간을 할당하기가 어려워진다. 요즘 컴퓨터는 페이징 개념을 도입해서 메모리를 관리하기 때문에 연속된 큰 공간을 할당하는 작업도 무리 없이 해내긴 하지만 그렇다 하더라도 배열은 자기가 당장 쓰지 않는 공간까지 전부 예약해두고 있어야 하기 때문에 공간 낭비가 생긴다. 또한 배열은 처음에 정한 배열의 용량(크기)을 모두 사용했을 경우에 데이터를 배열에 추가하기 위해서는, 확장된 크기의 배열을 메모리에 추가로 확보한 후에 기존에 있던 배열의 내용을 새 배열에 복사하고 새 데이터를 새로운 배열에 추가한 후에 기존 배열을 없애야 한다. 따라서 배열은 크기의 조정이 쉽지 않다. 반면에 연결 리스트는 그저 새 노드를 기존 리스트에 연결하면 될 뿐이다. 한마디로 연결 리스트는 메모리 공간을 필요한 만큼은 사용할 수 있다는 장점이 있다.

3. 구현 방법[편집]


일반적으로 구조체와 그 포인터로 구성된다. 포인터가 없는 Java언어의 경우에는 포인터 역할을 수행하는 레퍼런스로 구현한다.


3.1. 단순 연결 리스트 (Singly Linked List)[편집]


파일:external/upload.wikimedia.org/400px-Single_linked_list.png
다음 노드에 대한 참조만을 가진 가장 단순한 형태의 연결 리스트이다. 가장 마지막 원소를 찾으려면 얄짤없이 리스트 끝까지 찾아가야 하기 때문에(O(n)), 마지막 원소를 가리키는 참조를 따로 가지는 형태의 변형도 있다. 보통 (Queue)를 구현할 때 이런 방법을 쓴다.

이 자료구조는 Head 노드 (첫번째 데이터 노드)를 참조하는 주소를 잃어버릴 경우 데이터 전체를 못 쓰게 되는 단점이 있다. 다음 노드를 참조하는 주소 중 하나가 잘못되는 경우에도 체인이 끊어진 양 거기부터 뒤쪽 자료들을 유실한다. 따라서 안정적인 자료구조는 아니다.

파일 시스템 중 FAT 파일 시스템이 이 단순 연결 리스트로 파일 청크를 연결하는데 그래서 FAT 파일 시스템은 파일 내용 일부가 손상될 경우 파일의 상당 부분을 유실할 수 있고 랜덤 액세스 성능도 낮다.


3.2. 이중 연결 리스트 (Doubly Linked List)[편집]


파일:external/upload.wikimedia.org/400px-Doubly_linked_list.png
다음 노드의 참조뿐만 아니라 이전 노드의 참조도 같이 가리키게 하면 이중 연결 리스트가 된다. 뒤로 탐색하는 게 가능하다는 단순한 장점 이외에도 한 가지 장점이 더 있는데, 단순 연결 리스트는 현재 가리키고 있는 노드를 삭제하는 게 한 번에 안 되고 O(n)이 될 수밖에 없는데 비해[4]이중 연결 리스트에서 현재 노드를 삭제하는 것은 훨씬 간단하다. 대신 관리해야 할 참조가 두 개나 있기 때문에 삽입이나 정렬의 경우 작업량이 더 많고 자료구조의 크기가 약간 더 커진다.

단일 연결 리스트보다는 손상에 강한 편이다. Head 노드 (첫번째 노드)와 Tail 노드 (마지막 노드)를 갖고 있다면 둘 중 하나를 가지고 전체 리스트를 순회할 수 있기 때문에 끊어진 체인을 복구하는 게 가능하다. 단일 연결 리스트는 Tail 노드로는 리스트 순회가 불가능하고 (이전 노드로의 연결이 없기 때문에) Head 노드 유실시 전체 자료를 다 잃어버린다. 단점은 이런 보정 알고리즘을 구현하지 않았을 경우에는 오히려 손상에 더 취약해진다는 것이다. 예를 들어 노드의 next 포인터 (다음 노드의 메모리 주소를 갖는 포인터, 즉 다음 노드를 가르키는 포인터)는 갱신을 했는데 prev 포인터(이전 노드를 가르키는 포인터)는 갱신하지 않았을 경우 prev포인터를 따라가는 순회에서 도달 불가능한 '잃어버린' 노드가 발생한다.

3.3. 순환 연결 리스트 (Circular linked list)[편집]


파일:external/upload.wikimedia.org/400px-Circurlar_linked_list.png
단순 연결 리스트 (Singly linked list)에서 마지막 원소(노드)가 (Null) 대신 처음 원소를 가리키게 하면 순환 연결 리스트가 된다. 이와 비슷하게, 이중 연결 리스트 (Doubly linked list)의 처음과 끝을 서로 이으면 이중 순환 연결 리스트를 만들 수 있다.

스트림 버퍼의 구현에 많이 사용한다. 이미 할당된 메모리 공간을 삭제하고 재할당하는 부담이 없기 때문에 를 구현하는 데에도 적합하다.


3.4. 청크 리스트(Chunked List)[편집]


배열과 리스트의 장점을 합친 것. 리스트의 멤버가 배열이다. CPU에 캐시 기능이 있는 경우 지역성Locality이 떨어지는 연결 리스트는 심각한 성능 저하를 불러온다. 이를 보완하기 위해 리스트의 멤버를 레코드의 배열로 하는 것이다. 이 청크 리스트의 발전형이 바로 B+tree다.


4. 분석[편집]


배열과는 달리 첫번째 데이터의 추가/삭제가 O(1)의 시간안에 수행된다. 배열의 경우 데이터를 추가 또는 삭제할 때 해당 지점 뒤쪽의 데이터가 모두 추가 혹은 삭제되는 데이터 크기만큼 밀리거나 당겨져야 하나 연결 리스트는 그럴 필요가 없다. 하지만, 첫번째가 아닌 중간에 있는 데이터들의 추가/삭제는 해당 데이터가 있는 노드까지 도달하는 데 시간이 걸리기 때문에 (삭제하고자 하는 노드까지 가려면 Head 노드, 즉 첫번째 노드부터 출발해서 그 다음 노드 또 그 다음 노드...처럼 하나씩 이동해야 한다.) O(n)의 수행시간을 갖게된다.

같은 이유로 데이터 탐색시에는 문제가 되는데 각 데이터에 한번에 접근할 수가 없어 항상 순차적으로 도달해야 한다. 이 때문에 데이터 검색에 쥐약이다. 정렬은 O(nlogn)시간에 가능하다. 이 단점을 해결한 것이 위에서 여러 번 언급한 그 B+tree.

5. C에서의 활용[편집]


구조체를 활용해서 정의한다.

struct Linked_List 
{
    int number;
    char name[20];
    ...
    struct Linked_List *next;
};


연결리스트 데이터 하나의 노드는 구조체 인스턴스 한 개에 대응한다. 이 안에 넣고자 하는 데이터(숫자, 문자 등등...)을 넣고, 거기에 (이전 노드와) 다음 노드의 위치를 표시해주는 포인터를 포함시키면 연결리스트가 정의된다. 위 예시는 단일 연결 리스트의 예로서, 여기에서 Previous를 추가하면 이중 연결 리스트가 된다.

struct Linked_List* Previous;


사용할 때는 head->next->next->name 하는 식으로 사용한다. 노드 조회 메서드에 대해 설명하자면

List * get(int index) 
{
  List *head = HEAD;
  List *cur = head;

  for(int i=0; i<index; ++i) 
  {
    cur = cur->next;
  }
  return cur;
}


이런 식으로 사용한다.

특이할 점은 노드 자신의 위치를 표시하기 위한 자기참조 구조체라는 개념이다. 자기 자신을 구조체 멤버로 가지지 못하는 대신 자기 자신 타입의 포인터를 멤버로 가짐으로써, 스스로의 주소값을 표현하는 것이다. 그렇다면 왜 구조체가 자기 자신을 멤버로 못 가지는가? 구조체는 항상 고정 크기의 메모리 공간을 할당받아야 하는데 자기 자신을 멤버로 가진다면 그 '고정 크기'를 알 수 없기 때문이다. 그러나 자기 자신에 대한 포인터는 크기가 항상 4바이트(32비트 OS)나 8바이트(64비트 OS)로 같다. 그래서 구조체의 전체 크기를 계산 가능하다.

그럼 Java에서는 어떻게 자기 자신을 멤버로 포함시키는가 하면, 자바의 클래스는 레퍼런스 참조를 한다. 레퍼런스라 함은 상수 타입의 포인터와 같은 개념이다. 레퍼런스는 상수이면서 연산이 불가능한 포인터와 동일하다.

6. C++에서의 활용[편집]


C++은 STL의 list가 연결 리스트를 제공한다. 템플릿이므로 원하는 자료형으로 정의해 사용할 수 있다.

직접 구현할경우 C++에서 추가된 클래스 기능을 이용하여 정의하는 걸 빼면 C에서와 별반 다를 것은 없다. 다만 클래스는 스트럭트 타입의 변수와는 달리 안에 함수를 정의 가능하므로, 자주 쓰는 기능을 클래스 함수로 정의해놓은 후 조금 더 쾌적하게 연결리스트를 사용할 수 있다.


7. LISP와 연결 리스트[편집]


LISP에서 기본으로 제공하는 리스트는 기본적으로 리스트를 생성하는 cons 연산에 의해 결합된 cell 이 연결된 단순 연결형 리스트이다. 구현 상 리스트에 원소를 추가할 때에는 앞쪽에 붙는다. 예를 들어 (1 2 3) 이라는 리스트는 (cons 1 (cons 2 (cons 3 nil))) 이라는 표현과 동일하다.

여기에 리스트의 첫 번째 요소를 가져오는 car, 첫 번째를 제외한 나머지 리스트를 구하는 cdr을 사용하여 루프를 돌면 매우 편리하게 리스트를 사용할 수 있다. 리스트가 비었는지 알아보려면 car 을 수행하여 첫 번째 요소가 nil 인지 검사하면 되고, 길이를 구하려면 nil이 나올때 까지 루프를 돌면서 1씩 더한다든지 하는 식이다. (물론 이는 단순한 것을 예로 든 것으로, 이러한 기본적인 함수는 이미 다 구현되어 제공된다.)

사투리 중에서는 필요성에 의해 양방향 리스트를 다른 자료형으로 구현하여 단순 연결형 리스트와 함께 기본 데이터 타입으로 지원하는 경우도 종종 있다. 이 경우는 리스트라고 안부르고 벡터 등 다른 명칭으로 불린다.

파일:크리에이티브 커먼즈 라이선스__CC.png 이 문서의 내용 중 전체 또는 일부는 2023-12-09 12:53:26에 나무위키 연결 리스트 문서에서 가져왔습니다.

[1] 배열: 영희 뒤에 지혜를 배치하려면 민수부터 하나씩 뒤로 밀고 지혜를 배치한 다음에 번호를 다시 매겨야겠다. / 연결리스트: 지혜는 영희 뒤로 가.[2] 배열: 3번 나와봐. / 연결리스트: 어디보자 철수가 1번이고, 그 다음에 있는 영희가 2번이고, 그 다음이 민수니까 민수가 3번이구나![3] 이 때문에 Java의 LinkedList의 경우처럼 배열처럼 인덱스 번호로 연산을 수행할 수 있도록 get(index)의 형태로 메서드를 제공하기도 한다. 물론 이것도 wrapping 수준이지 함수 자체를 까고 들어가보면 역시 순서대로 찾는 것과 같다.[4] '현재' 노드를 삭제하기 위해서는 '이전' 노드가 가리키는 다음 공간이 '다음' 노드가 되도록 설정해야 한다. 즉, '현재' 노드를 삭제하면서 '현재' 노드의 위치에 '다음' 노드를 위치시켜야 된다는 의미이다.