리스트 : 데이터를 순서대로 나열해 놓은 자료구조
선형 리스트 / 연결 리스트 : 가장 단순한 구조를 가진 리스트
데이터가 사슬 모양으로 연결한 형태. 하나 이상의 칸을 건너 뛸 수 없음
배열의 각 요소에 데이터가 순서대로 저장되어 있으므로
'1만큼 큰 인덱스를 갖는 요소'에 접근
데이터의 삽입 시 삽입 요소 다음의 모든 요소를 하나씩 뒤로 밀어야 하고,
데이터의 삭제 시 모든 요소를 뒤로 밀거나 앞으로 당겨야 함
선형리스트의 문제
1. 쌓이는 데이터의 크기를 미리 알아야 함
2. 데이터의 삽입, 삭제에 따라 데이터를 모두 옮겨야 하기 때문에 효율이 좋지 않음
연결 리스트에 데이터를 삽입할 때 노드용 객체를 만들고, 삭제할 때 노드용 객체를 없애면 앞에서 제시한 데이터를 밀고 당기는 문제를 해결할 수 있음
typedef struct __node {
Member data; /* 데이터 */
struct __node *next; /* 다음 노드를 가리키는 포인터 */
} Node;
* 자기 참조(self-referential)형 자료 구조 : 자기 자신과 같은 자료형의 객체를 가리키는 데이터를 가지고 있는 자료구조
연결 리스트를 관리하며 두 멤버로 구성되어 있고 모두 Node에 대한 포인터 자료형을 가지고 있음
/* 포인터를 사용한 선형 리스트(헤더) */
#ifndef ___LinkedList
#define ___LinkedList
#include "Member.h"
/*--- 노드 ---*/
typedef struct __node {
Member data; /* 데이터 */
struct __node *next; /* 뒤쪽 포인터(뒤쪽 노드에 대한 포인터) */
} Node;
/*--- 선형 리스트 ---*/
typedef struct {
Node *head; /* 머리 노드에 대한 포인터 */
Node *crnt; /* 선택한 노드에 대한 포인터 */
} List;
/*--- 선형 리스트를 초기화 ---*/
void Initialize(List *list);
/*--- 함수 compare로 x와 같은 노드를 검색 ---*/
Node *search(List *list, const Member *x, int compare(const Member *x, const Member *y));
/*--- 머리에 노드를 삽입 ---*/
void InsertFront(List *list, const Member *x);
/*--- 꼬리에 노드를 삽입 ---*/
void InsertRear(List *list, const Member *x);
/*--- 머리 노드를 삭제 ---*/
void RemoveFront(List *list);
/*--- 꼬리 노드를 삭제 ---*/
void RemoveRear(List *list);
/*--- 선택한 노드를 삭제 ---*/
void RemoveCurrent(List *list);
/*--- 모든 노드를 삭제 ---*/
void Clear(List *list);
/*--- 선택한 노드의 데이터를 출력 ---*/
void PrintCurrent(const List *list);
/*--- 선택한 노드의 데이터를 출력(줄바꿈 문자 포함) ---*/
void PrintLnCurrent(const List *list);
/*--- 모든 노드의 데이터를 리스트 순서대로 출력 ---*/
void Print(const List *list);
/*--- 선형 리스트 종료 ---*/
void Terminate(List *list);
#endif
head : 연결 리스트의 머리 노드를 가리키는 포인터
crnt : 현재 선택한 노드를 가리키는 포인터. '검색'한 노드를 선택하고 '삭제'하는 용도
Node형 객체를 만들고 만든 객체의 포인터를 반환
static Node *AllocNode(void)
{
return calloc(1, sizeof(Node));
}
Node형 객체의 두 멤버(data, next)의 값을 설정하는 함수
첫 번째 매개변수 n으로 전달받은 포인터가 가리키는 Node형 객체에 x가 가리키는 값을 대입하고 n의 next에 세 번째 매개변수로 전달받은 next를 대입함
static void SetNode(Node *n, const Member *x, const Node *next)
{
n->data = *x; /* 데이터 */
n->next = next; /* 뒤쪽 포인터 */
}
연결 리스트를 사용하기 전에 초기화하는 함수
void Initialize(List *list)
{
list->head = NULL; /* 머리 노드 */
list->crnt = NULL; /* 선택한 노드 */
}
머리 노드를 가리키는 list->head에 NULL 값을 대입하여 노드가 하나도 없는 텅 빈 연결 리스트 생성. 빈 연결 리스트는 노드가 하나도 없는 상태이므로 head가 가리키는 노드도 존재하지 않음
* list->crnt에도 NULL 값을 대입하여 노드를 선택하지 않은 상태로 초기화
연결리스트가 비어 있는지 판단하는 방법 [a]
[a]는 노드가 하나도 없는 빈 연결 리스트
list->head == NULL /* 연결 리스트가 비어 있는지 확인합니다. */
노드가 1개인 연결 리스트를 파난하는 방법 [b]
[b]는 연결 리스트에 노드가 1개만 있는 상태
list->head->next == NULL /* 노드가 1개인지 확인합니다. */
변수 list->head가 가리키는 노드는 머리 노드 A
연결 리스트에는 1개의 노드만 있기 때문에 머리 노드 A는 리스트의 꼬리 노드이기도 함
따라서 next의 값은 NULL
노드가 2개인 연결 리스트를 판단하는 방법[c]
[c]는 노드가 2개 있는 상태 - 머리 노드는 A, 꼬리 노드는 B
list->head->next->next == NULL /* 노드가 2개인지 확인합니다. */
list->head가 가리키는 노드 A의 next는 노드 B를 가리킴
꼬리 노드 B의 next는 NULL 값을 가지고 있음
머리 노드인지 판단하는 방법
자료평이 Node *형인 변수 p는 리스트의 노드 중 하나를 가리킴
p == list->head /* p가 가리키는 노드가 머리 노드인지 확인합니다. */
꼬리 노드인지 판단하는 방법
자료평이 Node *형인 변수 p는 리스트의 노드 중 하나를 가리킴
p->next == NULL /* p가 가리키는 노드가 꼬리 노드인지 확인합니다. */
어떤 조건을 만족하는 노드를 검색
Node *search(List *list, const Member *x, int compare(const Member *x, const Member *y))
{
/*l3*/ Node *ptr = list->head;
while (ptr != NULL) {
if (compare(&ptr->data, x) == 0) { /* 키값이 같은 경우 */
list->crnt = ptr;
return ptr; /* 검색 성공 */
}
/*l9*/ ptr = ptr->next; /* 다음 노드를 선택 */
}
/*l11*/ return NULL; /* 검색 실패 */
}
l3 : 스캔하고 있는 노드를 가리키는 변수 ptr을 list->head로 초기화
while문 : ptr 값이 NULL이면 스캔할 노드가 없음을 의미
while문 내 if문 : 스캔하고 있는 노드의 데이터(ptr->data_와 x가 가리키는 데이터 비교. 검색 성공 시 list->crnt에 ptr 대입 후 찾은 노드에 대한 포인터인 ptr 반환
l9 : ptr에 ptr->next를 대입하여 ptr이 다음 노드를 가리키도록 함. 이후 계속 스캔
l11 : 검색 실패 시 NULL값 반환
연결 리스트의 머리에 노드를 삽입하는 함수
void InsertFront(List *list, const Member *x)
{
Node *ptr = list->head;
list->head = list->crnt = AllocNode();
SetNode(list->head, x, ptr);
}
처리 과정
1. 삽입 전의 머리 노드 A에 대한 포인터를 ptr에 대입
2. 삽입할 노드 G를 AllocNode 함수로 만들고, 만든 노드 G를 가리키도록 list->head를 업데이트. list->crnt도 새로 만든 노드를 가리키도록 업데이트
3. SetNode 함수를 호출하여 값을 설정. 삽입한 다음 머리 노드의 다음을 가리키는 포인터의 값을 ptr로 업데이트
연결 리스트 꼬리에 노드를 삽입하는 함수
void InsertRear(List *list, const Member *x)
{
if (list->head == NULL) /* 비어있는 경우 */
InsertFront(list, x); /* 머리에 삽입 */
else {
/*[4]*/
Node *ptr = list->head;
while (ptr->next != NULL)
ptr = ptr->next;
/*[5]*/
ptr->next = list->crnt = AllocNode();
SetNode(ptr->next, x, NULL);
}
}
[4] 꼬리 노드 찾기. 머리 노드를 가리키도록 초기화한 ptr이 가리키는 노드를 계속해서 다음 노드를 가리키도록 업데이트하는 과정을 반복함으로써 노드를 처음부터 차례로 스캔.
ptr->next가 가리키는 노드가 NULL이 되면 while문 종료, 이때 ptr이 가리키는 노드는 꼬리 노드 F
[5] 삽입할 노드 G를 AllocNode 함수로 만들고, 삽입하기 전의 꼬리 노드 F의 다음 포인터 ptr->next가 가리키는 노드에 삽입한 다음의 꼬리 노드 G 대입. SetNode 함수를 호출해 앞에서 만든 노드 G의 값을 설정, 이때 노드 G의 다음 노드에 NULL 대입
머리 노드를 삭제하는 함수
void RemoveFront(List *list)
{
if (list->head != NULL) {
Node *ptr = list->head->next; /* 두 번째 노드에 대한 포인터 */
free(list->head); /* 머리 노드를 해제 */
list->head = list->crnt = ptr; /* 새로운 머리 노드 */
}
}
리스트가 비어 있지 않은 경우(list->head != NULL)에만 머리 노드 삭제
머리 노드에 대한 포인터 list->head에 두 번째 노드 B에 대한 포인터 list->head->next를 대입하여 list->head가 가리키는 노드를 B로 업데이트, 삭제하기 전의 머리 노드 A의 메모리 영역 해제. 이때 선택한 노드 crnt가 가리키는 노드도 B로 업데이트
리스트에 노드가 1개만 있는 경우, 삭제하기 전의 머리 노드는 꼬리 노드이므로 다음 노드를 가리키는 list->head->next의 값은 NULL
꼬리 노드를 삭제하는 함수
void RemoveRear(List *list)
{
if (list->head != NULL) {
if ((list->head)->next == NULL) /* 노드가 하나만 있는 경우 */
RemoveFront(list); /* 머리 노드를 삭제 */
else {
/*[1]*/
Node *ptr = list->head;
Node *pre = NULL;
while (ptr->next != NULL) {
pre = ptr;
ptr = ptr->next;
}
/*[2]*/
pre->next = NULL; /* pre는 꼬리 노드부터 두 번째 노드 */
free(ptr); /* ptr은 꼬리 노드 */
list->crnt = pre;
}
}
}
리스트에 있는 노드 개수에 따라 해당하는 작업을 수행
리스트에 노드가 1개만 있는 경우 : 머리 노드를 삭제하는 것과 같음
RemoveFront 함수로 처리
리스트에 노드가 2개 이상 있는 경우
[1] '꼬리 노드'와 '꼬리 노드로부터 두 번째 노드' 찾기
InsertRear 함수와 비슷하나 현재 스캔하고 있는 노드이 '앞에 있는 노드'를 가리키는 변수 pre 추가
[2] 꼬리 노드부터 두 번째 노드 E의 다음을 가리키는 포인터에 NULL을 대입하고 꼬리 노드 F의 메모리 영역 해제, 이때 선택한 노드 crnt가 가리키는 노드는 삭제한 다음의 꼬리 노드 pre가 됨
while문이 종료되면 pre는 노드 E를, ptr은 노드 F를 가리킴
현재 선택한 노드(list->crnt)가 가리키는 노드를 삭제하는 함수
void RemoveCurrent(List *list)
{
if (list->head != NULL) {
if (list->crnt == list->head) /* 머리 노드를 선택한 상태라면 */
RemoveFront(list); /* 머리 노드를 삭제 */
else {
/*[1]*/
Node *ptr = list->head;
while (ptr->next != list->crnt)
ptr = ptr->next;
/*[2]*/
ptr->next = list->crnt->next;
free(list->crnt);
list->crnt = ptr;
}
}
}
선택한 노드가 머리 노드인지 아닌지에 따라 다음의 작업 수행
연결 리스트의 모든 노드를 삭제하는 함수
void Clear(List *list)
{
while (list->head != NULL) /* 텅 빌 때까지 */
RemoveFront(list); /* 머리 노드를 삭제 */
list->crnt = NULL;
}
연결 리스트가 완전히 텅 빈 상태(head == NULL)가 될 때까지 머리 요소의 삭제 작업 반복, 이때 모든 노드를 삭제하면 리스트가 완전히 빈 상태가 되므로 crnt의 값도 NULL로 업데이트됨
선택한 노드의 데이터를 출력하는 함수
/*--- 선택한 노드의 데이터를 출력 ---*/
void PrintCurrent(const List *list)
{
if (list->crnt == NULL)
printf("선택한 노드가 없습니다.");
else
PrintMember(&list->crnt->data);
}
/*--- 선택한 노드의 데이터를 출력(줄바꿈 문자 포함) ---*/
void PrintLnCurrent(const List *list)
{
PrintCurrent(list);
putchar('\n');
}
선택한 노드가 없는 경우(list->crnt == NULL) '선택한 노드가 없습니다.' 출력
리스트의 모든 노드를 순서대로 출력하는 함수
void Print(const List *list)
{
if (list->head == NULL)
puts("노드가 없습니다.");
else {
Node *ptr = list->head;
puts("【 모두 보기 】");
while (ptr != NULL) {
PrintLnMember(&ptr->data);
ptr = ptr->next; /* 뒤쪽 노드 선택 */
}
}
}
머리 노드부터 꼬리 노드까지 포인터 ptr이 가리키는 데이터 출력
void Terminate(List *list)
{
Clear(list); /* 모든 노드를 삭제 */
}
모든 노드를 삭제하는 Clear 함수 호출
/*---노드---*/
typedef struct __node {
Member data; /* 데이터 */
struct __node *next; /* 다음 노드를 가리키는 포인터 */
} Node;
/* 선형 리스트를 사용하는 프로그램 */
#include <stdio.h>
#include "Member.h"
#include "LinkedList.h"
/*--- 메뉴 ---*/
typedef enum {
TERMINATE, INS_FRONT, INS_REAR, RMV_FRONT, RMV_REAR, PRINT_CRNT,
RMV_CRNT, SRCH_NO, SRCH_NAME, PRINT_ALL, CLEAR
} Menu;
/*--- 메뉴 선택 ---*/
Menu SelectMenu(void)
{
int i, ch;
char *mstring[] = {
"머리에 노드를 삽입", "꼬리에 노드를 삽입", "머리 노드를 삭제",
"꼬리 노드를 삭제", "선택한 노드를 출력", "선택한 노드를 삭제",
"번호로 검색", "이름으로 검색", "모든 노드를 출력",
"모든 노드를 삭제",
};
do {
for (i = TERMINATE; i < CLEAR; i++) {
printf("(%2d) %-18.18s ", i + 1, mstring[i]);
if ((i % 3) == 2)
putchar('\n');
}
printf("( 0) 종료 : ");
scanf("%d", &ch);
} while (ch < TERMINATE || ch > CLEAR);
return (Menu)ch;
}
/*--- 메인 ---*/
int main(void)
{
Menu menu;
List list;
Initialize(&list); /* 선형 리스트를 초기화 */
do {
Member x;
switch (menu = SelectMenu()) {
/* 머리에 노드를 삽입 */
case INS_FRONT:
x = ScanMember("머리에 삽입", MEMBER_NO | MEMBER_NAME);
InsertFront(&list, &x);
break;
/* 꼬리에 노드를 삽입 */
case INS_REAR:
x = ScanMember("꼬리에 삽입", MEMBER_NO | MEMBER_NAME);
InsertRear(&list, &x);
break;
/* 머리 노드를 삭제 */
case RMV_FRONT:
RemoveFront(&list);
break;
/* 꼬리 노드를 삭제 */
case RMV_REAR:
RemoveRear(&list);
break;
/* 선택한 노드의 데이터를 출력*/
case PRINT_CRNT:
PrintLnCurrent(&list);
break;
/* 선택한 노드를 삭제 */
case RMV_CRNT:
RemoveCurrent(&list);
break;
/* 번호로 검색 */
case SRCH_NO:
x = ScanMember("검색", MEMBER_NO);
if (search(&list, &x, MemberNoCmp) != NULL)
PrintLnCurrent(&list);
else
puts("그 번호의 데이터가 없습니다.");
break;
/* 이름으로 검색 */
case SRCH_NAME:
x = ScanMember("검색", MEMBER_NAME);
if (search(&list, &x, MemberNameCmp) != NULL)
PrintLnCurrent(&list);
else
puts("그 이름의 데이터가 없습니다.");
break;
/* 모든 노드의 데이터를 출력 */
case PRINT_ALL:
Print(&list);
break;
/* 모든 노드를 삭제 */
case CLEAR:
Clear(&list);
break;
}
} while (menu != TERMINATE);
Terminate(&list); /* 선형 리스트의 뒤처리 */
return 0;
}