*본 내용은 [윤성우의 열혈 자료구조] 책과 강의를 보고 공부하면서 요점 정리한 내용입니다.
단순 연결 리스트의 마지막 노드는 NULL을 가리킨다.
원형 연결 리스트의 마지막 노드는 첫 번째 노드를 가리킨다.
머리에 노드 추가
꼬리에 노드 추가
단순 연결 리스트처럼 head와 tail 포인터 변수를 각각 두지 않고, 하나의 포인터 변수만 있어도 머리 또는 꼬리에 노드를 간단히 추가할 수 있다.
변형된 원형 연결 리스트
장점을 부각시키기 위해 head보다 tail을 자주 사용한다. 이를 변형된 원형 연결 리스트라고 하고 원형 리스트의 일반적인 모델이다.
변형된 원형 연결 리스트의 꼬리를 가리키는 포인터 변수는 tail
, 머리를 가리키는 포인터 변수는 tail->next
이다.
조회관련 LFirst: 이전에 구현한 연결 리스트와 기능 동일
조회관련 LNext: 원형 연결 리스트를 계속해서 순환하는 형태로 변경
(원형 연결 리스트는 그 구조상 끝이 존재하지 않는다. 따라서 LNext 함수는 계속해서 호출이 가능하고, 이로 인해서 리스트를 순환하면서 저장된 값을 반환하도록 구현한다.)
삭제관련 LRemove: 이전에 구현한 연결 리스트와 기능 동일
삽입관련: 앞과 뒤에 삽입이 가능하도록 두 개의 함수 정의
정렬 관련: 정렬과 관련된 부분 전부 제거
이외의 부분: 이전에 구현한 연결 리스트와 기능 동일
typedef int Data;
// 노드 구조체
typedef struct _node
{
Data data;
struct _node * next;
} Node;
// 원형 연결 리스트 구조체
typedef struct _CLL
{
Node * tail;
Node * cur; // 참조 위한 것
Node * before; // 삭제 위한 것
int numOfData;
} CList;
typedef CList List;
void ListInit(List * plist);
void LInsert(List * plist, Data data); // 노드를 꼬리에 추가
void LInsertFront(List * plist, Data data); // 노드를 머리에 추가
int LFirst(List * plist, Data * pdata);
int LNext(List * plist, Data * pdata);
Data LRemove(List * plist);
int LCount(List * plist);
void ListInit(List * plist)
{
// 모든 멤버를 NULL(포인터 번수)와 0(int형 변수)로 초기화한다.
// (cur과 before은 실질적인 초기화는 다른 곳에서 하므로 여기서 초기화하지 않아도 괜찮다.)
plist->tail = NULL;
plist->cur = NULL; // 실질적인 초기화는 LFirst에서
plist->before = NULL;
plist->numOfData = 0;
}
[첫 번째 노드 삽입]
첫 번째 노드는 그 자체로 머리이자 꼬리이기때문에 노드를 뒤에 추가하던 앞에 추가하던 그 결과가 동일하다.
// LInsert & LInsertFront의 공통부분
void LInsert~(List * plist, Data data)
{
Node * newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
if(plist->tail == NULL) // 첫 번째 노드라면
{
plist->tail = newNode;
newNode->next = newNode;
}
else // 두 번째 이후의 노드라면
{
/*** 차이가 나는 부분 ***/
}
(plist->numOfData)++;
}
[두 번째 이후 노드 머리로 삽입]
// 노드를 머리에 추가
void LInsertFront(List * plist, Data data)
{
Node * newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
if(plist->tail == NULL)
{
plist->tail = newNode;
newNode->next = newNode;
}
else // 두 번째 이후 노드 머리로 삽입
{
newNode->next = plist->tail->next;
plist->tail->next = newNode;
}
(plist->numOfData)++;
}
[앞과 뒤의 삽입 과정 비교]
[두 번째 이후 노드 꼬리로 삽입]
//노드를 꼬리에 추가
void LInsert(List * plist, Data data)
{
Node * newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
if(plist->tail == NULL)
{
plist->tail = newNode;
newNode->next = newNode;
}
else // 두 번째 이후 노드 꼬리로 삽입
{
newNode->next = plist->tail->next;
plist->tail->next = newNode;
plist->tail = newNode; // LInsertFront 함수에는 없는 문장. 유일한 차이점.
}
(plist->numOfData)++;
}
함수 호출 결과, 삭제 과정을 위해 cur의 왼쪽 노드는 bofore이어야 한다.
int LFirst(List * plist, Data * pdata)
{
if(plist->tail == NULL) // 저장된 노드가 없다면
return FALSE;
plist->before = plist->tail;
plist->cur = plist->tail->next;
*pdata = plist->cur->data;
return TRUE;
}
원형 연결 리스트이므로 리스트의 끝을 검사하는 코드가 없다.
int LNext(List * plist, Data * pdata)
{
if(plist->tail == NULL) // 저장된 노드가 없다면
return FALSE;
plist->before = plist->cur; // before 한 칸 이동
plist->cur = plist->cur->next; // cur 이동
*pdata = plist->cur->data;
return TRUE;
}
LData LRemove(List * plist)
{
Node * rpos = plist->cur;
LData rdata = rpos->data;
plist->before->next = plist->cur->next; // 핵심연산1
plist->cur = plist->before; // 핵심연산2
free(rpos);
(plist->numOfData)--;
return rdata;
}
Data LRemove(List * plist)
{
Node * rpos = plist->cur; // 아래 if문에서 꼬리인지 확인하기 위해 rpos에 저장
Data rdata = rpos->data;
if(rpos == plist->tail) // 삭제 대상을 tail이 가리킨다면
{
if(plist->tail == plist->tail->next) // 그리고 마지막 남은 노드라면
plist->tail = NULL;
else // 마지막 노드가 아니라면
plist->tail = plist->before; // tail 왼쪽으로 이동
}
/*** 단순 연결 리스트와 동일한 코드 ***/
plist->before->next = plist->cur->next;
plist->cur = plist->before;
free(rpos);
(plist->numOfData)--;
return rdata;
}
typedef struct _node
{
Data data;
struct _node * next; // 양방향으로 포인터가 필요하다.
struct _node * prev;
}
※ 문제 05-2도 나중에 직접 풀어보자
typedef int Data;
typedef struct _node
{
Data data;
struct _node * next;
struct _node * prev;
} Node;
// 양방향 연결 리스트 구조체
typedef struct _dbLinkedList
{
Node * head;
Node * cur; // 참조 위해
int numOfData;
} DBLinkedList;
typedef DBLinkedList List;
void ListInit(List * plist);
void LInsert(List * plist, Data data);
int LFirst(List * plist, Data * pdata);
int LNext(List * plist, Data * pdata);
int LPrevious(List * plist, Data * pdata); // LNext 함수와 반대로 이동함
int LCount(List * plist);
int main(void)
{
// 양방향 연결 리스트의 생성 및 초기화 ///////
List list;
int data;
ListInit(&list);
// 8개의 데이터 저장 ///////
LInsert(&list, 1); LInsert(&list, 2);
LInsert(&list, 3); LInsert(&list, 4);
LInsert(&list, 5); LInsert(&list, 6);
LInsert(&list, 7); LInsert(&list, 8);
// 추가된 이후의 연결 순서
// head -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1
// 저장된 데이터의 조회 ///////
if(LFirst(&list, &data))
{
printf("%d ", data);
// 오른쪽 노드로 이동하며 데이터 조회
while(LNext(&list, &data))
printf("%d ", data);
// 왼쪽 노드로 이동하며 데이터 조회
while(LPrevious(&list, &data))
printf("%d ", data);
printf("\n\n");
}
return 0;
}
실행 결과
8 7 6 5 4 3 2 1 2 3 4 5 6 7 8
typedef struct _dbLinkedList
{
Node * head;
Node * cur;
int numOfData;
} DBLinkedList;
void ListInit(List * plist)
{
plist->head = NULL;
plist->numOfData = 0;
}
[첫 번째 노드의 삽입]
// 첫 번째 노드의 추가 과정만을 담은 결과
void LInsert(List * plist, Data data)
{
Node * newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
// 아래 문장에서 plist->head는 NULL이다.
newNode->next = plist->head;
newNode->prev = NULL;
plist->head = newNode;
(plist->numOfData)++;
}
[두 번째 이후 노드의 삽입]
첫 번째 노드의 추가 과정에 덧붙여서, if문이 포함하는 문장이 두 번째 이후의 노드 추가 과정에서는 요구가 된다.
void LInsert(List * plist, Data data)
{
Node * newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = plist->head;
/***추가된 부분***/
if(plist->head != NULL)
plist->head->prev = newNode;
newNode->prev = NULL;
plist->head = newNode;
(plist->numOfData)++;
}
LFirst 함수와 LNext 함수는 사실상 단방향 연결 리스트의 경우와 차이가 없다. 그리고 LPrevious 함수는 LNext 함수와 이동 방향에서만 차이가 난다.
int LFirst(List * plist, Data * pdata)
{
if(plist->head == NULL)
return FALSE;
/***차이나는 부분***/
plist->cur = plist->head;
*pdata = plist->cur->data;
return TRUE;
}
int LNext(List * plist, Data * pdata)
{
if(plist->cur->next == NULL)
return FALSE;
/***차이나는 부분***/
plist->cur = plist->cur->next;
*pdata = plist->cur->data;
return TRUE;
}
int LPrevious(List * plist, Data * pdata)
{
if(plist->cur->prev == NULL)
return FALSE;
/***차이나는 부분***/
plist->cur = plist->cur->prev; // 노드 왼쪽으로 이동
*pdata = plist->cur->data;
return TRUE;
}