1. 원형 연결리스트

 

 ▣ 원형 연결 리스트의 이해

 

 

단순 연결리스트


단순 연결 리스트의 마지막 노드는 NULL을 가리킨다

원형 연결리스트

 

원형 연결 리스트의 마지막 노드는 첫 번째 노드를 가리킨다

원형 연결리스트 머리에 노드 추가

 

∙ 원형 연결리스트는 모든 노드가 원의 형태를 이루면서 연결되어 있기 때문에 원형 연결 리스트 에서는 사실상 머리와 꼬리의 구분이 없다

∙ 두 경우의 노드 연결 순서가 같으니, 원형 연결리스트는 head가 가리키는 노드가 다르다.

 

▣ 원형 연결 리스트의 장점 

 

“단순 연결 리스트처럼 머리와 꼬리를 가리키는 포인터 변수를 각각 두지 않아도,  (2개)
하나의 포인터 변수만 있어도 머리 또는 꼬리에 노드를 간단히 추가할 수 있다.”    (1개) 만으로도 가능

 

변형 중 원형 연결리스트

 

앞서 소개한 모델을 기반으로는 위의 장 점을 살리기 어렵다. 
그래서 우리는 변형 된, 그러나 보다 일반적이라고 인식이 되고 있는 변경된 원형 연결 리스트를 구현한다.

 

▣ 변형된 연결 리스트

 

변형된 연결 리스트

 

 꼬리를 가리키는 포인터 변수는? tail

∙ 머리를 가리키는 포인터 변수는? tail->next 

이렇듯 리스트의 꼬리와 머리의 주소 값을 쉽게 확인할 수 있기 때문에,
연결 리스트를 가리키는 포인터 변수는 하나만 있으면 된다.

 

▣ 변형된 연결 리스트변형된 원형 연결 리스트의 구현 범위


※ 원형 연결 리스트는 그 구조상 이 존재하지 않는다. 
따라서 LNext 함수는 계속해서 호출이 가능하고, 이로 인해서 리스트를 순환하면서 저장된 값을 반환하도록 구현한다.



▶ 구조체 정의

typedef struct _linkedList
{
	Node* tail;
	Node* cur;
	Node* before;

	int numOfData;

}LinkedList;

typedef LinkedList List;



▶ 초기화 함수

//초기화
void ListInit(List* pList)
{
	pList->tail;
	pList->cur = NULL;
	pList->before = NULL;
	pList->numOfData = 0;
}

모든 멤버를 NULL과 0으로 초기화한다.

 


▶ 노드 삽입 

void LInsertFront(List* pList, LData data)
{
	Node* newNode = new Node();
	newNode->data = data;

	if (pList->tail == NULL)
	{
		pList->tail = newNode;
		newNode->next = newNode;
	}
	else
	{

	}
	(pList->numOfData)++;
}

첫 번째 노드는 그 자체로 머리이자 꼬리이기 때문에 노드를 뒤에 추가하건 앞에 추가하건 그
결과가 동일하다

 두번쨰 이후 노드 삽입

void LInsertFront(List* pList, LData data)
{
	Node* newNode = new Node();
	newNode->data = data;

	if (pList->tail == NULL)
	{
		pList->tail = newNode;
		newNode->next = newNode;
	}
	else
	{
		newNode->next = pList->tail->next;  //1
		pList->tail->next = newNode;        //2
	}
	(pList->numOfData)++;
}

 


  원형 연결 리스트 구현: 앞과 뒤의 삽입 과정 비교

노드를 머리에 추가한 결과

 

노드를 꼬리에 추가한 결과

 

 

void LInsertTail(List* pList, LData data)
{
	Node* newNode = new Node();
	newNode->data = data;

	if (pList->tail == NULL)
	{
		//pList->tail = newNode;
		//newNode->next = newNode;

		pList->tail = newNode;
		newNode->next = newNode;

	}
	else
	{
		newNode->next = pList->tail->next;
		pList->tail->next = newNode;
		pList->tail = newNode;       //꼬리가 newNode 위치로 이동한다 (위 함수와 꼬리 위치가 차이점)
	}
	(pList->numOfData)++;
}

 


  원형 연결 리스트 구현: 조회

int LFirst(List* pList, LData* pdata)
{
	if (pList->tail == NULL)   
		return false;

	pList->before = pList->tail;     
	pList->cur = pList->tail->next;  

	*pdata = pList->cur->data;

	return true;
}

LFirst 함수 호출 결과

cur가 머리를 가리키게 한다

 

int LNext(List* pList, LData* pdata)
{
	if (pList->tail == NULL)   
		return false;

	pList->before = pList->cur;    
	pList->cur = pList->cur->next; 

	*pdata = pList->cur->data;
	return true;
}

※ 원형 연결 리스트이므로 리스트의 끝을 검사하는 코드가 없다

이어지는 LNext 호출결과

 


 

▶ 원형 연결 리스트 구현: 노드의 삭제

 

∙ 핵심연산 1. 삭제할 노드의 이전 노드가, 삭제할 노드의 다음 노드를 가리키게 한다.

∙ 핵심연산 2. 포인터 변수 cur을 한 칸 뒤로 이동시킨다.

 

//더미 연결리스트 삭제 코드
LData LRemove(List* pList)
{
	Node* rpos = pList->cur;
	LData rdata = rpos->data;


	pList->before->next = pList->cur->next;  //1
	pList->cur = pList->before;              //2


	delete rpos;
	(pList->numOfData)--;

	return rdata;
}

※ 더미 노드와 삭제하는 방법은 동일하다

 

  그림상으로는 두 연결 리스트의 삭제 과정이 비슷해 보이나
원형 연결 리스트에 는 더미 노드가 없기 때문에 삭제의 과정이 상황에 따라서 달라진다.

 

▶ 원형 연결 리스트 노드 삭제 구현

LData LRemove(List* pList)
{
	Node* rpos = pList->cur;
	LData rdata = rpos->data;

	//삭제할 노드를 tail이 가리킨다면
	if (rpos == pList->tail)
	{
		//그리고 마지막 노드라면
		if (pList->tail == pList->tail->next) //2
		{
			pList->tail == NULL;
		}
		else
		{
			pList->tail = pList->before;	   //1
		}
	}

	//단순 연결리스트와 동일한 코드
	pList->before->next = pList->cur->next;
	pList->cur = pList->before;

	delete rpos;
	(pList->numOfData)--;
	return rdata;
}

 


2. 양방향 연결리스트

 

 

 

 

 

typedef struct _node
{
	int data;  //현재 데이터
	struct _node * next;   //다음 노드를 가르킬 포인터  
	struct _node * precv;  //이전 노드를 가르킬 포인터  

} Node;

양방향 연결 리스트를 위한  노드 구조체 변경

 

 

▶ 양방향으로 노드를 연결하는 이유

 

2종류의 연결리스트

 

 ※ 단순 연결 리스트와 같이 before를 유지할 필요가 없다!

※ 오른쪽 노드로의 이동이 용이하다! 양방향으로 이동이 가능하다!

 위의 코드에서 보이듯이 양방향으로 연결한다 하여 더 복잡한 것은 아니다! 그렇게 느낀다면 선입견이다.

 

▶  구현할 양방향 연결 리스트 모델

 

 

 

  • LRemove 함수를 ADT에서 제외시킨다. 
  • 대신에 왼쪽 노드의 데이터를 참조하는 LPrevious 함수를 ADT에 추가시킨다.
  • 새 노드는 머리에 추가한다.

 

▶  연결 리스트의 구현: 리스트의 초기화

 

typedef struct _dblinkedList
{
	Node* head;
	Node* cur;
	int numOfData;

}_dblinkedList;

typedef _dblinkedList List;

ListInit 함수의 정의에 참조해야 하는 구조체의 정의

 

//초기화
void ListInit(List* pList)
{
	pList->head = NULL;
	pList->numOfData = 0;
}

멤버 cur은 조회의 과정에서 초기화 되는 멤버이니 head와 numOfData만 초기화 하면 된다

 

▶  연결 리스트의 구현: 노드 삽입

 

더미 노드를 기반으로 하지 않기 때문에 노드의 삽입 방법은 두 가지로 구분이 된다

 

▶  연결 리스트의 구현: 첫번쨰 노드 삽입

 

void LInsert(List* pList, LData data)
{
	Node* newNode = new Node();
	newNode->data = data;

	newNode->next = pList->head;
	newNode->precv = NULL;
	pList->head = newNode;
	
	(pList->numOfData)++;
}

 

첫 번째 노드의 추가 과정만을 담은 결과 

 

 

▶  연결 리스트의 구현: 두 번째 이후 노드의 삽입

void LInsert(List* pList, LData data)
{
	Node* newNode = new Node();
	newNode->data = data;

	newNode->next = pList->head;

	if (pList->head != NULL) 
	{   //(1/2)
		pList->head->precv = newNode;
	}

	//헤드 이동(2/2)
	newNode->precv = NULL;
	pList->head = newNode;
	
	(pList->numOfData)++;
}

 

첫 번째 노드의 추가 과정에 덧붙여서, if문이 포함하는 문장이 두 번째 이후의 노드 추가 과정에서는 요구가 된다. 

이미 들어있을 경우에는 헤드 이전에 추가를 하고, newNode의 이전을 NULL로 한뒤 헤드를 newNOde로 이동한다

 

 

 

▶  연결 리스트의 구현: 데이터 조회

 

int LFirst(List* pList, LData* pdata)
{
	if (pList->head == NULL)   
		return false;

	pList->cur = pList->head;  //처음

	*pdata = pList->cur->data;
	return true;
}

int LNext(List* pList, LData* pdata)
{
	if (pList->head == NULL)
		return false;

	pList->cur = pList->cur->next; //다음거

	*pdata = pList->cur->data;
	return true;
}

int LPreviout(List* pList, LData* pdata)
{
	if (pList->head == NULL)
		return false;

	pList->cur = pList->cur->precv;  //전에거

	*pdata = pList->cur->data;
	return true;
}

 

※ LFirst 함수와 LNext  함수는 사실상 단방향 연결 리스 트의 경우와 차이가 없다. 
   LPrevious 함수는 LNext 함수와 이동 방향에서만 차이가 난다

+ Recent posts