*본 내용은 [윤성우의 열혈 자료구조] 책과 강의를 보고 공부하면서 요점 정리한 내용입니다.
단순 연결 리스트의 마지막 노드는 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;
}