자료구조하면 정말 처음 나오지만, 참 중요한 링크드 리스트이다. 수업 시간 때 거진 이론만 배우고, 대충 이해하고 말아 아쉬운 부분이 있었는데, 이번에 윤성우씨 책으로 나름대로? 공부하고 있는 것 같다.

Linked List하면 이 그림이 제일 먼저 기억 날 것이다.
Node로 구성된 자료구조가 연결되어있는 구조이다.
typedef struct _node {
LData data;
struct _node *next;
}Node;
노드는 다음과 같이 구성되어 있다.
1. 데이터
2. 다음 노드 포인터
이 노드 포인터가 참 재밌는 역할을 한다.
ADT는 다음과 같다.
void ListInit(List * plist);
void LInsert(List * plist, LData data);
int LFirst(List *plist, LData *pdata);
int LNext(List *plist, LData *pdata);
void FInsert(List *plist, LData data);
void SInsert(List *plist, LData data);
LData LRemove(List *plist);
int LCount(List *plist);
void SetSortRule(List *plist, int(*comp)(LData d1, LData d2));
기본적으로 arraylist와 동일하지만 FInsert, SInsert, SetSortRule이라는 메소드가 추가되었다.
간단하게 설명하면 FInsert는 대소비교 X 삽입, SInsert는 대소비교 O 삽입, SetSortRule은 대소비교 여부 설정
여기서 특이한 부분은 SetSortRule인데, 생소한 [함수 포인터] 라는 개념이 등장한다. (쪽팔린 일이지만 처음본다!)
SetSortRule은 인자로 함수 포인터를 받기 때문에 comp 함수를 어떻게 설정하냐에 따라 프로그램을 바꿀 수 있다.
LinkedList 자료구조는 다음과 같이 구성되어 있다.
typedef struct _linkedList {
Node * head;
Node * cur;
Node * before;
int numOfData;
int(*comp)(LData d1, LData d2); //함수 포인터
}LinkdedList;
typedef LinkdedList List;
head는 맨 앞의 값을 가리키는 포인터가 될 것이다.
그 구조는 다음과 같다.

void ListInit(List * plist) {
plist->head = (Node *)malloc(sizeof(Node));
plist->head->next = NULL;
plist->comp = NULL;
plist->numOfData = 0;
}
코드 상에서는 첫 번째 node가 dummy로 사용되게 된다. 이는 Insert를 편리하게 하기 위해서이다. 만약 dummy 노드가 없다면 첫 번째 삽입과, 그 다음 삽이이 달라질 것이기 때문이다.
이제 Insert 메소드를 살펴보자.
void LInsert(List * plist, LData data) {
if (plist->comp == NULL)
FInsert(plist, data);
else
SInsert(plist, data);
}
void FInsert(List *plist, LData data) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = plist->head->next;
plist->head->next = newNode;
(plist->numOfData)++;
}
대소비교를 하는 SInsert는 나중에 보고 FInsert를 먼저 살펴보자.
단순히 노드하나를 삽입하는데 노드가 삽입되는 방향을 잘 보자.
우리는 통상 뒤로 줄을 서듯 줄줄히 붙을 것이라 생각하지만 이 코드에서는 dummy 노드 뒤에 newNode가 붙게된다.

다음과 같은 식으로 dummy node 뒤에 붙는다.
이제 LFirst와 LNext를 살펴보자
int LFirst(List *plist, LData *pdata) {
if (plist->head->next == NULL)
return false;
plist->before = plist->head; //더미 노드를 가리키고 있음
plist->cur = plist->head->next; //현재 노드를 가리키고 있음
*pdata = plist->cur->data; //데이터 반환
return true;
}
int LNext(List *plist, LData *pdata) {
if (plist->cur->next == NULL)
return false;
plist->before = plist->cur; //현재 노드
plist->cur = plist->cur->next; //그 다음 노드
*pdata = plist->cur->data;
return true;
}
First와 Next는 둘 다 특정한 Node를 가리키기 위한 메소드들이다.
각각 특정 노드를 before과 cur(현재위치)로 가리키는데, 처음을 가리킬 것인지, 아니면 다음값을 가리킬 것인지의 차이이다.
이 두 메소드는 Remove에서 사용된다.
LData LRemove(List *plist) {
Node *rpos = plist->cur;
LData rdata = rpos->data;
plist->before->next = plist->cur->next;
plist->cur = plist->before;
free(rpos);
(plist->numOfData)--;
return rdata;
}
지울 node를 저장하고, free로 메모리에서 해제해준다. 삭제된 노드의 이전노드의 next의 삭제된 노드의 next로 연결해주고, cur를 수정한다. 이 때 cur과 before은 같은 노드를 가리키게 되는데, 재조정 할 필요는 없다.
** cur을 수정하지 않으면, 참조되지 않은 상태의 노드를 가리키게 되는데, 이는 다음 next 호출 시에 넘어가버리는 문제가 생긴다!
이제 얼추 기본적인 기능의 소개는 끝났고, 대소비교 즉 정렬삽입을 소개하고 글을 마친다.
void SetSortRule(List *plist, int(*comp)(LData d1, LData d2)) { //comp를 초기화 하는 함수
plist->comp = comp;
}
단순히 plist->comp를 comp로 설정해준다.
void SInsert(List *plist, LData data) {
Node *newNode = (Node*)malloc(sizeof(Node));
Node *pred = plist->head; //더미 노드를 가리킴
newNode->data = data;
while (pred->next != NULL && plist->comp(data, pred->next->data) != 0) {
pred = pred->next;
}
newNode->next = pred->next;
pred->next = newNode;
(plist->numOfData)++;
}
comp(d1,d2)는 d1이 크면 1, d2가 크면 0을 반환한다.
pred는 dummy node에서 시작해, 현재 삽입하는 데이터가 더 큰 위치를 찾을 때까지, 가리키는 노드를 옮긴다.
(pred = pred->next)
찾게 되면 반복문을 탈출하고, 삽입을 진행한다.
코드 전체는 하나하나 설명하려니 글재주가 없어... 쉽지 않다. 아직 누구를 가르쳐줄 실력은 아닌거 같다.
멀고 먼 코딩의 길 ㅋㅋ
'자료구조' 카테고리의 다른 글
| 힙 (Heap) (0) | 2020.01.29 |
|---|---|
| 배열리스트 (ArrayList) (0) | 2020.01.26 |
| 재귀 - 하노이의 탑 (0) | 2020.01.22 |