Search

[자료구조] 연결 리스트의 응용

제목
Tag
작성일

연결리스트의 변형

단순 연결리스트의 단점

기본 구조
각 노드는 data 필드와 link(다음 노드 주소) 필드로 구성된다.
[data | link] → [data | link] → [data | NULL]
Plain Text
복사
단점
각 노드에서 후행 노드만 가리키므로 특정 노딍 선행 노드에 대한 접근을 헤드부터 재검색해야함
마지막 노드는 항상 NULL을 가리킴
→ 이런 문제점을 보완하기 위해 원형 연결 리스트와 이중 연결 리스트 나옴
종류
구조적 특징
장점
단점
단순 연결 리스트
노드가 data + link 로 구성, 단방향 연결
구현이 간단
역방향 탐색 불가
원형 연결 리스트
마지막 노드의 링크가 head를 가리킴
리스트 전체를 반복 탐색 가능
끝과 처음 구분 어려움
이중 연결 리스트
각 노드가 Llink, data, Rlink 가짐
양방향 탐색, 삽입·삭제 용이
메모리 추가 소모, 포인터 관리 복잡

원형 연결 리스트

1. 정의 및 생성

구조체
typedef struct ListNode { int data; struct ListNode* link; // 다음 노드 (원형이므로 마지막 노드의 link는 head) } listNode; typedef struct { listNode* head; // 공백이면 NULL } linkedList_h;
C
복사
생성 함수
linkedList_h* createLinkedList_h(void) { linkedList_h* H = (linkedList_h*)malloc(sizeof(linkedList_h)); H->head = NULL; return H; }
C
복사
ListNode: 원형 연결 리스트의 기본 노드 구조
linkedList_h: head 포인터만을 가진 헤드 노드 구조
createLinkedList_h(): 리스트를 동적 생성하고 head를 NULL로 초기

2. 노드 삽입

2-1. 공백 리스트일때
첫 노드를 삽입할 때는 head가 새 노드를 가리키고, link도 자기 자신을 가리키게 하여 원형 구조를 만든다.
if (H->head == NULL) { H->head = NewNode; NewNode->link = NewNode; // 자기 자신을 가리켜 원형 완성 return; } else: ...
C
복사
2-2. 원형 연결 리스트가 공백이 아닌 경우
else { tempNode = H->head; while (tempNode->link != H->head) tempNode = tempNode->link; // 마지막 노드 탐색 NewNode->link = tempNode->link; // 새 노드의 link = head tempNode->link = NewNode; // 마지막 노드의 link = 새 노드 H->head = NewNode; // head를 새 노드로 갱신 }
C
복사
2-3. 중간 위치 삽입
void addMiddleNode(linkedList_h* H, listNode* preNode, int itdata) { listNode* NewNode; NewNode = (listNode*)malloc(sizeof(listNode)); NewNode->data = itdata; NewNode->link = NULL; NewNode->link = preNode->link; // 새 노드가 preNode의 다음을 가리킴 preNode->link = NewNode; // preNode가 새 노드를 가리키게 수정 return; }
C
복사
예시
초기 리스트:
head → [10] → [20] → [30] ──┐ ↑ │ └─────────────────┘
Plain Text
복사
삽입: 데이터 5
포인터 흐름:
1.
tempNode는 [30]을 찾음 (마지막 노드)
2.
NewNode(5)->link = head(10)
3.
tempNode(30)->link = NewNode(5)
4.
head = NewNode(5)
결과:
head → [5] → [10] → [20] → [30] ↑ │ └────────────────────┘
Plain Text
복사

3. 노드 삭제

단일 원형 리스트에서 삭제 노드의 직전 노드(prev)를 찾아 그 prev->link 를 삭제 노드의 다음으로 바꾸는 것이 핵심이다. 삭제 대상이 head인 경우엔 head 갱신과 마지막 노드의 link 재연결이 함께 필요하다.
3-1. 삭제 대상 탐색 함수
void finddelCircularNode(linkedList_h* H, int itdata) { listNode* tempNode; listNode* prevNode; if (H->head == NULL) { // 공백 리스트 printf("Circular Linked List is EMPTY\n"); return; } // prevNode를 '마지막 노드'로 맞춰 둠 prevNode = H->head; while (prevNode->link != H->head) prevNode = prevNode->link; // 첫 노드부터 검색 tempNode = H->head; do { if (tempNode->data == itdata) { // 찾으면 삭제 함수 호출 deleteCircularNode(H, prevNode); return; } else { prevNode = tempNode; // 선행 노드 갱신 tempNode = tempNode->link; // 다음 노드로 } } while (tempNode != H->head); // 한 바퀴 돌고도 못 찾음 printf("Search Fail\n"); return; }
C
복사
공백이면 즉시 종료 메시지
prevNode를 마지막 노드로 맞춰 두고 시작하면, 첫 노드(head)가 곧바로 삭제 대상이어도 prevNode가 올바른 선행 노드가 됨
do–while로 한 바퀴 정확히 돌고, 못 찾으면 Search Fail
동작 예시
리스트: head → [10] → [20] → [30] → head
삭제 탐색값: 10
시작 시 prevNode=마지막(30), tempNode=head(10) → 바로 매치 → deleteCircularNode(H, prevNode) 호출 → head 삭제 정상 처리
3-2. 실제 삭제 함수
delNode = prevNode->link (삭제 대상은 선행 노드의 다음)
단일 노드: 자기 자신만 가리키면 head=NULL 후 free
일반: prevNode->link = delNode->link, 필요하면 head 갱신
void deleteCircularNode(linkedList_h* H, listNode* prevNode) { listNode* delNode; if (H->head == NULL) return; // 안전장치 delNode = prevNode->link; // 삭제 대상 노드 // (1) 노드가 1개뿐인 경우 if (delNode == H->head && delNode->link == H->head) { H->head = NULL; free(delNode); return; } // (2) 일반 경우 (선행 → 대상 → 다음) prevNode->link = delNode->link; // 대상 건너뛰어 연결 if (delNode == H->head) // 대상이 head라면 head 이동 H->head = delNode->link; free(delNode); return; }
C
복사
포인터 흐름 예시 1 — head(10) 삭제
초기: head → 10 → 20 → 30 → head
prevNode=30, delNode=10
prevNode->link = 20, head = 20, free(10)
결과: head → 20 → 30 → head
포인터 흐름 예시 2 — 중간(30) 삭제
초기: head → 10 → 20 → 30 → 40 → head
탐색 중 prevNode=20, delNode=30
prevNode->link = 40, head 변화 없음, free(30)
결과: head → 10 → 20 → 40 → head

이중 연결 리스트

1. 정의 및 생성

구조체 정의
typedef struct ListNode { struct ListNode* Llink; // 왼쪽(이전 노드) int data; // 데이터 필드 struct ListNode* Rlink; // 오른쪽(다음 노드) } listNode; typedef struct { listNode* Lhead; // 리스트의 첫 노드를 가리킴 listNode* Fhead; // 리스트의 마지막 노드를 가리킴 } linkedList_h;
C
복사
리스트 생성 함수
linkedList_h* createLinkedList_h(void) { linkedList_h* H; H = (linkedList_h*)malloc(sizeof(linkedList_h)); H->Lhead = NULL; // 첫 노드 H->Fhead = NULL; // 마지막 노드 return H; }
C
복사
생성 결과
H ─→ [Lhead | NULL] [Fhead | NULL]
Plain Text
복사
→ 아직 노드가 없으므로 양쪽 포인터 모두 NULL

2. 노드 삽입

이중 연결 리스트는 양방향 포인터 조정이 핵심
void insertDNode(linkedList_h* H, listNode* prevNode, int item) { listNode* NewNode; NewNode = (listNode*)malloc(sizeof(listNode)); NewNode->data = item; NewNode->Llink = NULL; NewNode->Rlink = NULL; // 리스트가 공백이면 첫 노드 삽입 if (H->Lhead == NULL) { H->Lhead = NewNode; H->Fhead = NewNode; return; } // 일반 삽입 (prevNode 뒤) NewNode->Rlink = prevNode->Rlink; // 새 노드 오른쪽을 prevNode의 오른쪽으로 if (prevNode->Rlink != NULL) prevNode->Rlink->Llink = NewNode; // 기존 다음 노드가 있다면 왼쪽 연결 수정 prevNode->Rlink = NewNode; // prevNode 오른쪽 → 새 노드 NewNode->Llink = prevNode; // 새 노드 왼쪽 → prevNode // 만약 prevNode가 마지막 노드라면 Fhead 갱신 if (prevNode == H->Fhead) H->Fhead = NewNode; }
C
복사
예시
초기:
[Lhead] → [100] ↔ [200] ↔ [300] ← [Fhead]
Plain Text
복사
삽입: prevNode = [200], item = 250
포인터 변화:
1.
NewNode(250)->Rlink = prevNode(200)->Rlink(300)
2.
prevNode(200)->Rlink = NewNode(250)
3.
NewNode(250)->Llink = prevNode(200)
4.
nextNode(300)->Llink = NewNode(250)
결과:
[Lhead] → [100] ↔ [200] ↔ [250] ↔ [300] ← [Fhead]
Plain Text
복사

3. 노드 삭제

void deleteDNode(linkedList_h* H, listNode* delNode) { delNode->Llink->Rlink = delNode->Rlink; delNode->Rlink->Llink = delNode->Llink; free(delNode); }
C
복사
포인터 흐름
1.
delNode->Llink->Rlink = delNode->Rlink
→ 왼쪽 노드의 오른쪽 포인터가 삭제 노드의 오른쪽 노드를 가리킴
2.
delNode->Rlink->Llink = delNode->Llink
→ 오른쪽 노드의 왼쪽 포인터가 삭제 노드의 왼쪽 노드를 가리킴
3.
free(delNode)
→ 삭제 노드 메모리 해제
예시
초기:
[Lhead] → [100] ↔ [200] ↔ [300] ↔ [400] ← [Fhead]
Plain Text
복사
삭제: delNode = [300]
포인터 조정:
[200].Rlink = [400] [400].Llink = [200] free([300])
Plain Text
복사
결과:
[Lhead] → [100] ↔ [200] ↔ [400] ← [Fhead]
Plain Text
복사