본문 바로가기

대학교 과제/자료구조 [ 2 - 1 ]

자료구조 10주차 과제 - 이중 원형 연결리스트

반응형

1. 과제 안내문


이번 과제는 이중 연결 리스트에 대한 과제입니다. 이중 연결 리스트에서 앞/뒤로 이동하는 것과 삽입/삭제에 대한 연산입니다. 이중 연결 리스트는 원하는 대로 앞/뒤로 이동이 가능하기 때문에 꼭 첫 노드만 가리킬 필요가 없습니다. 어느 한 노드만 알고 있으면 원하는 대로 이동 가능하기 때문입니다. 이번 과제에는 이중 연결 리스트에 새로운 노드를 삽입하고, 기존의 노드를 삭제하고, 앞뒤로 이동하면서 data를 출력하는 내용입니다. 이번 과제에는 앞/뒤로 이동이 가능한 이중 연결 리스트이고, 또한 마지막 노드가 첫 노드를 가르키는 원형 연결 리스트이고, 헤드 노드는 없는 연결 리스트입니다. 아래의 연산자들을 완성하기 바랍니다. 다음은 명령에 대한 설명이다.

L ;; pList를 왼쪽으로 움직인다.

R ;; pList를 오른쪽으로 움직인다.

I n ;; pList오른쪽n을 가진 노드를 삽입한다.

D ;; pList오른쪽 노드를 삭제한다.

M n ;; pListn번 오른쪽(음수이면 왼쪽)으로 움직인다.

O n ;; pListn번 움직이고 해당 노드의 데이터를 반환한다.


 솔직히 말하면 이번 과제는 자료구조를 응용하는 것이 아닌 말 그대로 이중원형연결리스트가 가져야할 기본적인 기능이나 메소드를 구현하는 것이다. ( 심지어 이중원형 연결리스트는 학교 강의로 구현함......) 다음은 예시 실행 결과이다.

 

2. 실행 결과 예시


다음은 DebugONdefine하여 실행한 결과입니다.

I 186: <186>

I 191: <186> 191

L : 186 <191>

I 32: 32 186 <191>

I 31: 31 32 186 <191>

M -2: 31 <32> 186 191

I 184: 31 <32> 184 186 191

M -2: 31 32 184 186 <191>

I 206: 31 32 184 186 <191> 206

R : 31 32 184 186 191 <206>

I 208: 31 32 184 186 191 <206> 208

R : 31 32 184 186 191 206 <208>

I 217: 31 32 184 186 191 206 <208> 217

R : 31 32 184 186 191 206 208 <217>

I 247: 31 32 184 186 191 206 208 <217> 247

I 218: 31 32 184 186 191 206 208 <217> 218 247

R : 31 32 184 186 191 206 208 217 <218> 247

I 246: 31 32 184 186 191 206 208 217 <218> 246 247

M 4: 31 <32> 184 186 191 206 208 217 218 246 247

I 176: 31 <32> 176 184 186 191 206 208 217 218 246 247

M 4: 31 32 176 184 186 <191> 206 208 217 218 246 247

I 192: 31 32 176 184 186 <191> 192 206 208 217 218 246 247

L : 31 32 176 184 <186> 191 192 206 208 217 218 246 247

I 190: 31 32 176 184 <186> 190 191 192 206 208 217 218 246 247

M 3: 31 32 176 184 186 190 191 <192> 206 208 217 218 246 247

I 204: 31 32 176 184 186 190 191 <192> 204 206 208 217 218 246 247

I 196: 31 32 176 184 186 190 191 <192> 196 204 206 208 217 218 246 247

M 6: 31 32 176 184 186 190 191 192 196 204 206 208 217 <218> 246 247

I 229: 31 32 176 184 186 190 191 192 196 204 206 208 217 <218> 229 246 247

M 3: 31 32 176 184 186 190 191 192 196 204 206 208 217 218 229 246 <247>

D : 32 176 184 186 190 191 192 196 204 206 208 217 218 229 246 <247>

M 2: 32 <176> 184 186 190 191 192 196 204 206 208 217 218 229 246 247

I 179: 32 <176> 179 184 186 190 191 192 196 204 206 208 217 218 229 246 247

L : <32> 176 179 184 186 190 191 192 196 204 206 208 217 218 229 246 247

I 161: <32> 161 176 179 184 186 190 191 192 196 204 206 208 217 218 229 246 247

R : 32 <161> 176 179 184 186 190 191 192 196 204 206 208 217 218 229 246 247

I 170: 32 <161> 170 176 179 184 186 190 191 192 196 204 206 208 217 218 229 246 247

M 5: 32 161 170 176 179 184 <186> 190 191 192 196 204 206 208 217 218 229 246 247

I 189: 32 161 170 176 179 184 <186> 189 190 191 192 196 204 206 208 217 218 229 246 247

M -8: 32 161 170 176 179 184 186 189 190 191 192 196 204 206 208 217 218 229 <246> 247

D : 32 161 170 176 179 184 186 189 190 191 192 196 204 206 208 217 218 229 <246>

M -5: 32 161 170 176 179 184 186 189 190 191 192 196 204 <206> 208 217 218 229 246

I 207: 32 161 170 176 179 184 186 189 190 191 192 196 204 <206> 207 208 217 218 229 246

M 8: 32 <161> 170 176 179 184 186 189 190 191 192 196 204 206 207 208 217 218 229 246

I 166: 32 <161> 166 170 176 179 184 186 189 190 191 192 196 204 206 207 208 217 218 229 246

M 4: 32 161 166 170 176 <179> 184 186 189 190 191 192 196 204 206 207 208 217 218 229 246

I 180: 32 161 166 170 176 <179> 180 184 186 189 190 191 192 196 204 206 207 208 217 218 229 246

R : 32 161 166 170 176 179 <180> 184 186 189 190 191 192 196 204 206 207 208 217 218 229 246

I 183: 32 161 166 170 176 179 <180> 183 184 186 189 190 191 192 196 204 206 207 208 217 218 229 246

M 4: 32 161 166 170 176 179 180 183 184 186 <189> 190 191 192 196 204 206 207 208 217 218 229 246

D : 32 161 166 170 176 179 180 183 184 186 <189> 191 192 196 204 206 207 208 217 218 229 246

O -4: 32 161 166 170 176 179 <180> 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O -7: 32 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 217 218 229 <246>

O 10: 32 161 166 170 176 179 180 183 184 <186> 189 191 192 196 204 206 207 208 217 218 229 246

O 8: 32 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 <208> 217 218 229 246

O -6: 32 161 166 170 176 179 180 183 184 186 189 <191> 192 196 204 206 207 208 217 218 229 246

O -10: 32 <161> 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O -1: <32> 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O -9: 32 161 166 170 176 179 180 183 184 186 189 191 192 <196> 204 206 207 208 217 218 229 246

O 6: 32 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 217 <218> 229 246

O 10: 32 161 166 170 176 179 180 <183> 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O 8: 32 161 166 170 176 179 180 183 184 186 189 191 192 196 204 <206> 207 208 217 218 229 246

O -10: 32 161 166 170 176 <179> 180 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O -2: 32 161 166 <170> 176 179 180 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O 5: 32 161 166 170 176 179 180 183 <184> 186 189 191 192 196 204 206 207 208 217 218 229 246

O -6: 32 161 <166> 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O -2: <32> 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O -10: 32 161 166 170 176 179 180 183 184 186 189 191 <192> 196 204 206 207 208 217 218 229 246

O 2: 32 161 166 170 176 179 180 183 184 186 189 191 192 196 <204> 206 207 208 217 218 229 246

O -10: 32 161 166 170 <176> 179 180 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O -6: 32 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 217 218 <229> 246

O -10: 32 161 166 170 176 179 180 183 184 186 <189> 191 192 196 204 206 207 208 217 218 229 246

O 2: 32 161 166 170 176 179 180 183 184 186 189 191 <192> 196 204 206 207 208 217 218 229 246

O -6: 32 161 166 170 176 179 <180> 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O 10: 32 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 <207> 208 217 218 229 246

O -10: 32 161 166 170 176 179 <180> 183 184 186 189 191 192 196 204 206 207 208 217 218 229 246

O -10: 32 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 <217> 218 229 246

Exit : 32 161 166 170 176 179 180 183 184 186 189 191 192 196 204 206 207 208 <217> 218 229 246

??? ???? ?????

 

연결 리스트를 조작하는 과정을 단계마다 출력하면 효과적으로 프로그램할 수 있는데 이를 위하여 #define DebugON을 정의하여(중간 과정의 출력을 원하면 코드에서 주석 제거) 단계별로 출력하도록 하고, 어느 정도 프로그래밍이 완성되면 이 줄을 주석으로 처리하여 결과만 나오게 하면 된다.


 처음은 시행 결과만을 봤을 때, 맨탈 나가서 불닭볶음면 먹었다 ㅎ. 하지만 찬찬히 뜯어 보면 "그렇게 어려운 과제는 아닐 수도 있겠다" 라는 생각이 든다. 각각의 기능을 하는 함수만 구현하면 저 위의 프로그램은 자동적으로 시행되도록 교수님이 main()함수를 구현하셨다. 결국 내가 할 일은 각각의 함수만을 구현하는것 ㅎ. 그렇게 구현하게 되면 프로그램 마지막에 ??? ???? ?????가 어떤 문장인지 알 수 있다는 것이다. (CTF문제 푸는 기분이어서 재밌었다 ㅎㅎ) 

#define DebugOn부분을 주석처리하면 마지막에 우리가 알아야하는 문장만이 출력되며, 우리가 코드를 짤 때는 주석처리하지 않고 실행하여 과정을 보며 짜라고 하신 깊은 뜻이 담겨져있는 과제였다.

 

 그럼 위의 내용을 정리해보자

  1. main() 함수는 구현되어 있으며, 내가 해야할 일은 각각의 메소드를 담당하는 함수만을 작성하며 된다는 것. 
  2. 작성하면서 함수를 테스트 할때는 #define DebugOn을 주석처리하지 않고 전체 실행결과를 확인하며 코딩을 진행.
  3. 모든 함수를 작성하면 #define DebugOn을 주석처리하여 결과로 나오는 텍스트를 확인.

 사실 2번과 3번의 원리는 main()함수를 봐도 알지 못했다...... 이부분은 구글링을 통해 알아보도록 결심하고 일단 코드를 작성하기 시작했다.

 

3. 코드작성

 

 코드 작성에 들어가기 앞서 간단하게 이중원형연결리스트에 대한 개념을 확인하고자 한다. 9주차 과제를 푸는 게시물을 보면 알겠지만 단일 연결리스트는 한 방향만을 가리킨다. 그러나 이중 원형 연결리스트는 양쪽의 리스트를 가리키는 구조를 가지게 된다. 그림으로 표현하면 다음과 같다.

이중 원형 연결 리스트

 단일 원형 연결리스트와 조금 다른 점이 있다면 하나의 위치만을 알아도 왼쪽이든 오른쪽이든 어느곳으로나 갈 수 있다는 점이다. 때문에 리스트를 가리키는 포인터가 단일 연결리스트에서는 맨 처음 노드를 가리켰다면 이중 원형 연결리스트는 그 리스트에 속한 어떠한 노드를 가리키던 원하는 데이터를 찾을 수 있고 원하는 노드로 갈 수 있다는 점이다.

 

자 그러면 대충 이중 원형 연결리스트에 대한 내용을 대충 이해했으니 코드를 짜보도록 하자. 더욱 자세한 개념은 따로 올릴 수 있기를 바란다 ㅠㅠ

 

- 노드를 표현하는 구조체

 

 다음은 교수님이 정의하신 노드의 구조체이다.

typedef struct node {
	int nData;
	struct node *llink;
	struct node *rlink;
}	Node, *NodePtr;

 코드를 봤을 때 직관적으로도 느낄 수 있겠지만 llink, rlink는 각각 소속되어있는 노드의 왼쪽과 오른쪽을 가리키는 link이다.

 

-moveRight(), moveLeft() 함수

 

 다음은 교수님이 주신 함수의 헤더와 그 함수를 설명하는 주석이다.

int MoveRight(NodePtr& pList)
{	// 오른쪽으로 한번 이동한다.
	// 성공(실패)하면 true(false)를 return한다.
	return true;
}

int MoveLeft(NodePtr& pList)
{	// 왼쪽으로 한번 이동한다.
	// 성공(실패)하면 true(false)를 return한다.
	return true;
}

 

 오른쪽으로 이동한다는 것은 전달받은 pList포인터의 값이 오른쪽 노드를 가리키게 한다면 되는 것이다.  다만 중요한 점은 만약 노드가 하나만 남았을 때이다. 그 때만 false를 반환한다면 오류를 막을 수 있을 것이다. 다음은 내가 작성한 코드이다.

 

int MoveRight(NodePtr& pList)
{
	if (pList->rlink == pList) return false;
	pList = pList->rlink;
	return true;
}

int MoveLeft(NodePtr& pList)
{
	if (pList->llink == pList) return false;
	pList = pList->llink;
	return true;
}

 

-InsterNode() 함수 구현

 

 이제 교수님이 제공해주신 함수의 원형을 보여주지 않고 바로 내가 작성한 코드를 게시하겠다. 

int InsertNode(NodePtr& pList, int nData)
{
	NodePtr pNew = new Node;
	if (pNew) {
		pNew->nData = nData;
		if (pList) {
			NodePtr pLeft = pList;
			NodePtr pRight = pList->rlink;
			pNew->llink = pLeft;
			pNew->rlink = pRight;
			pLeft->rlink = pRight->llink = pNew;
		}
		else pList = pNew->llink = pNew->rlink = pNew;
	}
	return (int) pNew;
}

 이 InsertNode()함수에서 중요한 점은 바로 빈 리스트의 처리이다. 아무것도 pList가 아무것고 가리키지 않았을 때의 상황을 생각해야 하며, pNew가 새로운 메모리를 할당 받지 못했을 경우도 생각해야 한다 때문에 pNew가 메모리를 할당 받았을 때만 실행하기 위해 if문을 사용하였다.

 다음에 생각해야 할 점은 바로 pList가 비어있일 때이다. 비어있을 때는 오히려 더 간단할 수도 있다. 새로 할당받은 노드에 데이터를 넣고 모든 링크부분이 자기 자신을 가리키면 되기 때문이다.

 리스트가 존재할 경우가 중요하다. 순서를 작성해 보자면,

  1. 새로운 노드에 데이터 저장.
  2. 새로운 노드가 먼저 양쪽 노드를 기리킨다.
  3. 양쪽 노드가 새로운 노드를 가리킨다.

이런식으로 실행하면 될 것이다. 그림으로 정리하자면 다음과 같다.

이중 원형리스트의 추가

-DeleteNode() 함수 구현

 

 DeleteNode() 함수의 원리를 먼저 설명하자면 간단하다. 삭제할 노드를 가리키는 del이라는 포인터를 선언하고, 삭제될 노드의 양쪽에 있는 노드들을 서로 연결시켜주면 되는 것이다. 그림으로 표현하면 다음과 같다.

이중 원형리스트의 삭제

 의외로 코드는 간단하다 다만 노드가 한개일 때와 노드가 여러개일 때 (2개 이상일때)를 나눠줘야한다는 점이 가장 중요한 부분이다. 다음은 내가 작성한 코드이다.

int DeleteNode(NodePtr& pList)
{
	if (pList) {
		NodePtr pDel;
		if (pList != pList->llink) {
			pDel = pList->rlink;
			NodePtr pLeft = pList;
			NodePtr pRight = pDel->rlink;

			pLeft->rlink = pRight;
			pRight->llink = pLeft;
		}
		else {
			pDel = pList;
			pList = NULL;
		}

		delete pDel;
	}
	else return false;

	return true;
}

- MoveRepeatdely() 함수 구현

 

 이 함수는 입력된 정수만큼 pList포인터의 위치를 옮겨주는 함수이다. +2 면 오른쪽으로 두칸 -4이면 왼쪽으로 네칸 움직인다. 일단 입력된 정수가 양수인지, 음수인지를 구분하고 오른쪽, 왼쪽으로 옮겨주는 코드를 작성하였다. 움직이는 동작은 미리 만들어놓은 MoveLeft(), MoveRight()함수를 이용하였다. 다음은 내가 초기에 작성한 코드이다.

int MoveRepeatedly(NodePtr& pList, int nCtr) {

	if (nCtr > 0) {

		for (int i = 0; i < nCtr; i++) {

			if(MoveRight(pList) != true){
				
				return false;
			}
		}
	}
	else {

		for (int i = 0; i > nCtr; i++) {

			if (MoveLeft(pList) != true) {

				return false;
			}
		}
	}
	return true;
}

 

 다시 한번 말하지만 우리 교수님은 메모리를 아끼는 간결한 코드를 매우 좋아하신다. 그래서 나는 최대한 코드를 간결하게 짜기위해 노력했고 다음과 같이 바꿀 수 있었다.

 

int MoveRepeatedly(NodePtr& pList, int nCtr)
{
	if (nCtr > 0) {
		for (int i = 0; i < nCtr; i++) 
			if (!MoveRight(pList)) return false;
	}
	else {
		for (int i = 0; i > nCtr; i--) 
			if (!MoveLeft(pList)) return false;
	}
	return true;
}

 

 굳이 if문 안에 비교연산자를 쓸 필요가 없었다. nCtr이 음수일 때 그것을 다시 양수로 만들려는 사람들을 많이 봤지만 그럴필요 없이 for문의 조건문과 연산문을 살짝만 손봐주면 한줄을 줄일 수 있다. ㅎㅎ

 

- 최종 코드

 

 다음은 완성된 전체 코드이다. OutputData() 함수는 너무 간단해서 설명을 생략하였으며, "CmdList.h" 헤더 파일은 우리 교수님이 하루에 걸쳐 만드셨다고 하셔서 마음대로 올리면 안될 것 같아 올리지 않았다. (사실 main() 함수 올리는 것도 마음에 걸린다 ㅠㅠ) 만약 교수님을 따로 찾아뵈어 허락을 받는다면 올릴 수 있길 바란다.

 

#include <stdio.h>
#include <stdlib.h>

#define DebugON
#pragma warning (disable: 4996)

typedef struct node {
	int nData;
	struct node *llink;
	struct node *rlink;
}	Node, *NodePtr;

#include "CmdList.h"

void main()		// 헤드 노드가 없는 원형 이중 연결 리스트
{
	int InsertNode(NodePtr& pList, int nData);
	int DeleteNode(NodePtr& pList);
	int MoveRight(NodePtr& pList);
	int MoveLeft(NodePtr& pList);
	int MoveRepeatedly(NodePtr& pList, int nCtr);
	int OutputData(NodePtr& pList, int nCtr);
	void PrintList(NodePtr pList, const char *pCmnd);

	int nNdx = 0;
	int isGo = true;
	int bSuccess = true;
	NodePtr pList = NULL;
	char sMsg[100], *pMsg = sMsg;
	while (isGo) {
		int nData;
		const char *pCmnd = sCmnds[nNdx++];		// 명령어를 하나씩 가져온다.
		switch (pCmnd[0] | 0x20) {			// 소문자로
		case 'i':							// 새로운 노드를 오른쪽에 삽입
			nData = atoi(pCmnd + 1);
			bSuccess = InsertNode(pList, nData);
			break;
		case 'd':							// 오른쪽의 노드를 삭제
			bSuccess = DeleteNode(pList);
			break;
		case 'l':							// 왼쪽으로 한번 이동
			bSuccess = MoveLeft(pList);
			break;
		case 'r':							// 오른쪽으로 한번 이동
			bSuccess = MoveRight(pList);
			break;
		case 'm':							// 오른(+)/왼(-)쪽으로 n번 이동
			nData = atoi(pCmnd + 1);
			bSuccess = MoveRepeatedly(pList, nData);
			break;
		case 'o':							// n번 이동 후, 해당 노드의 data를 반환
			nData = atoi(pCmnd + 1);
			if ((nData = OutputData(pList, nData)) >= 0)
				*pMsg++ = nData;
			else
				bSuccess = false;
			break;
		case 'e':							// 끝
			bSuccess = true;
			isGo = false;
			break;
		default:
			bSuccess = false;
		}
		if (bSuccess == false)
			printf("---> %d-th command(%s) error, ....\n", nNdx, pCmnd);
#ifdef  DebugON
		PrintList(pList, pCmnd);
#endif
	}
	*pMsg = NULL;
	printf("%s\n\n", sMsg);
}

int MoveRight(NodePtr& pList)
{
	if (pList->rlink == pList) return false;
	pList = pList->rlink;
	return true;
}

int MoveLeft(NodePtr& pList)
{
	if (pList->llink == pList) return false;
	pList = pList->llink;
	return true;
}

int InsertNode(NodePtr& pList, int nData)
{
	NodePtr pNew = new Node;
	if (pNew) {
		pNew->nData = nData;
		if (pList) {
			NodePtr pLeft = pList;
			NodePtr pRight = pList->rlink;
			pNew->llink = pLeft;
			pNew->rlink = pRight;
			pLeft->rlink = pRight->llink = pNew;
		}
		else pList = pNew->llink = pNew->rlink = pNew;
	}
	return (int) pNew;
}

int DeleteNode(NodePtr& pList)
{
	if (pList) {
		NodePtr pDel;
		if (pList != pList->llink) {
			pDel = pList->rlink;
			NodePtr pLeft = pList;
			NodePtr pRight = pDel->rlink;

			pLeft->rlink = pRight;
			pRight->llink = pLeft;
		}
		else {
			pDel = pList;
			pList = NULL;
		}

		delete pDel;
	}
	else return false;

	return true;
}

int MoveRepeatedly(NodePtr& pList, int nCtr)
{
	if (nCtr > 0) {
		for (int i = 0; i < nCtr; i++) 
			if (!MoveRight(pList)) return false;
	}
	else {
		for (int i = 0; i > nCtr; i--) 
			if (!MoveLeft(pList)) return false;
	}
	return true;
}

int OutputData(NodePtr& pList, int nCtr)
{
	if (MoveRepeatedly(pList, nCtr)) return pList->nData;
	return -1;
}

void PrintList(NodePtr pList, const char *pCmnd)
{	// 데이터가 제일 작은 노드부터 출력한다.
	printf("%s: ", pCmnd);
	if (pList) {
		NodePtr pFirst = pList;
		while (pFirst->nData < pFirst->rlink->nData)	// 데이터가 제일 작은 노드를 찾는다
			pFirst = pFirst->rlink;
		pFirst = pFirst->rlink;				// 첫 노드
		NodePtr pNode = pFirst;
		do {								// 첫 노드부터 출력한다
			if (pNode == pList)
				putchar('<');				// pList 노드 표시
			printf("%d", pNode->nData);
			if (pNode == pList)
				putchar('>');				// pList 노드 표시
			putchar(' ');
			pNode = pNode->rlink;
		} while (pNode != pFirst);
	}
	putchar('\n');
}

 

4. 결어

 

 사실 이 과제 안내문을 처음 봤을 때는 엄청난 난이도의 문제인줄 알았지만 아니었다. 다행이도 교수님이 만드신 코드에서 내가 매운 자료구조의 기능만을 구현하면 완벽하게 돌아가는 코드였으며, 그렇게 어려운 기능도 아니었다. 여기서 생긴 궁금증도 이 블로그에 정리하면서 해결했으면 좋겠다.

반응형