책정리/뇌를 자극하는 알고리즘

1장 리스트

GONII 2015. 3. 18. 11:02
  • 링크드 리스트

    배열 유감

    배열처럼 데이터 집합을 보관하는 기능을 가지면서도 한편으로는 배열과는 달리 유연하게 크기를 바꿀 수 있는 자료구조

    리스트는 스택과 큐, 그리고 트리와 같은 자료구조를 이해할 수 있는 기반이 된다는 점에서 중요

    링크드 리스트를 소개합니다

    링크드 리스트 : 노드를 연결해서 만드는 리스트

    노드 : 데이터를 보관하는 필드 + 다음 연결 고리 역할을 하는 포인터 로 구성됨

       

    헤드(머리)와 테일(꼬리)을 가지고 있음

    데이터가 늘어날 때마다 테일에 붙이면 됨

    C 언어로 표현하는 링크드 리스트의 노드

    typedef struct Node

    {

    int Data; // 데이터 필드

    struct Node* NextNode ; // 다음 노드를 가리키는 포인터

    } NODE ;

    링크드 리스트의 주요 연산

    • 노드 생성 / 소멸

      지역 변수를 사용하면 함수가 끝남과 동시에 객체가 소멸되므로 malloc을 이용해 동적할당

      // 노드생성 함수

      Node* SLL CreateNode(ElementType NewData)

      {

      Node* NewNode = (Node*)malloc(sizeof(Node));

      NewNode->Data NewData ; // 데이터를 저장한다.

      NewNode- >NextNode NULL; // 다음 노드에 대한 포인터는 NULL로 초기화한다.

      return NewNode; // 노드의 주소를 반환한다

      }

         

      // 노드소멸 함수

      void SLL DestroyNode (Node* Node)

      {

      free (Node ) ; // malloc으로 동적할당했던 node를 해제

      }

    • 노드 추가

      // 노드 추가 함수

      void SLL_AppendNode( NODE** head, NODE* NewNode )

      {

      // 헤드 노드가 null 이면 새로운 노드가 head

      if ( *head == NULL )

      {

      *head = NewNode ;

      }

      else

      {

      // 테일을 찾아 연결

      NODE* tail = (*head);

      while( tail->NextNode != NULL )

      {

      tail = tail->NextNode;

      }

      tail->NextNode = NewNode ;

      }

      }

    • 노드 탐색

      리스트의 단점

      배열이 인덱스를 이용하여 즉시 원하는 요소를 취할 수 있게 하는데 반해, 링크드 리스트는 헤드부터 시작해서 다음 노드에 대한 포인터를 차근차근 노드의 수를 세어나가야만 원하는 요소에 접근할 수 있음

      // 노드 탐색 함수

      NODE* SLL_GetNodeAt( NODE* head, int location )

      {

      NODE* current = head ;

         

      while( current != NULL && (--location) >= 0 )

      {

      current = current->NextNode;

      }

         

      return current ;

      }

    • 노드 삭제

      // 노드 제거 함수

      void SLL_RemoveNode( NODE** head, NODE* remove )

      {

      if( *head == remove )

      {

      *head = remove->NextNode;

      }

      else

      {

      NODE* current = *head;

         

      while( current != NULL && current->NextNode != remove )

      {

      current = current->NextNode;

      }

         

      if( current != NULL )

      {

      current->NextNode = remove->NextNode;

      }

      }

      }

      삭제한 노드는 SLL_DestroyNode 함수로 소멸해줌....

    • 노드 삽입

      // 노드 삽입 함수

      void SLL_InsertAfter( NODE* current, NODE* NewNode )

      {

      NewNode->NextNode = current->NextNode;

      current->NextNode = NewNode ;

      }

    • 노드 개수 세기

      // 노드 개수 세기

      int SLL_GetNodeCount( NODE* head )

      {

      int count = 0;

      NODE* current = head;

         

      while( current != NULL )

      {

      current = current->NextNode;

      count++

      }

         

      return count;

      }

      링크드 리스트 예제 프로그램

    • 예제 LinkedList.h

#ifndef LINKEDLIST_H

#define LINKEDLIST_H

   

#include <stdio.h>

#include <stdlib.h>

   

typedef int ElementType;

   

typedef struct tagNode

{

ElementType data;

struct tagNode* next;

} NODE;

   

//

NODE* SLL_createNode( EelementType data ) ;

void SLL_destroyNode( NODE* node );

void SLL_appendNode( NODE** head, NODE* newNode );

void SLL_insertAfter( NODE* current, NODE* newNode );

void SLL_insertNewHead( NODE** head, NODE* newHead );

void SLL_removeNode( NODE** head, NODE* remove );

NODE* SLL_getNode( NODE* head, int location ) ;

int SLL_getCount( NODE* head ) ;

   

#endif

  • 예제 LinkedList.c

#include "SingleLinkedList.h"

   

// 노드 생성

NODE* SLL_createNode( EelementType data )

{

NODE* newNode = (NODE*)malloc(sizeof(NODE));

newNode->data = data;

newNode->next = NULL;

   

return newNode;

}

   

// 노드 소멸

void SLL_destroyNode( NODE* node )

{

free(node);

}

   

// 노드 추가

void SLL_appendNode( NODE** head, NODE* newNode )

{

// 헤드가 NULL이면 새로운 노드는 head

if( (*head) == NULL )

{

*head = newNode;

}

else

{

// 테[일을 찾아 연결

NODE* tail = (*head);

while( tail->next != NULL )

{

tail = tail->next;

}

tail->next = newNode;

}

}

   

// 노드 삽입

void SLL_insertAfter( NODE* current, NODE* newNode )

{

newNode->next = current->next;

current->next = newNode;

}

   

// 새로운 헤드

void SLL_insertNewHead( NODE** head, NODE* newHead )

{

if( *head == NULL )

{

(*head) = newHead;

}

else

{

newHead->next = (*head);

(*head) = newNode;

}

}

   

// 노드 제거

void SLL_removeNode( NODE** head, NODE* remove )

{

if( *head == remove )

{

*head = remove->next;

}

else

{

NODE* current = *head;

while( current != NULL && current->next != remove )

{

current = current->next;

}

   

if( current != NULL )

{

current->next = remove->next;

}

}

}

   

// 노드ㅡ 탐섹

NODE* SLL_getNode( NODE* head, int location )

{

NODE* current = head;

   

while( current != NULL && (--location) >= 0 )

{

current = current->next;

}

   

return current;

}

int SLL_getCount( NODE* head )

{

int count = 0 ;

NODE* current = head;

   

while( current != NULL )

{

current = current->next;

count++;

}

   

return count;

}

  • 예제 main.c

#include "SingleLinkedLists.h"

   

void main(void)

{

int i = 0 ;

int count = 0 ;

NODE* list = NULL;

NODE* current = NULL;

NODE* newNode = NULL;

   

//추가

for( i = 0 ; i < 5 ; i++ )

{

newNode = SLL_createNode(i);

SLL_appendNode( &list, newNode);

}

   

newNode = SLL_createNode(-1);

SLL_insertNewHead( &list, newNode ) ;

   

newNode = SLL_createNode(-2);

SLL_insertNewHead( &list, newNode ) ;

   

// 출력

count = SLL_getCount( list ) ;

for ( i = 0 ; i < count ; i++ )

{

current = SLL_getNode( list, i );

printf("list[%d] : %d\n", i, current->data;);

}

   

// 3번째 뒤에 삽입

current = SLL_getNode( list, 2 ) ;

newNode = SLL_createNode( 3000 ) ;

SLL_insertAfter( current, newNode ) ;

   

//출력

// 출력

count = SLL_getCount( list ) ;

for ( i = 0 ; i < count ; i++ )

{

current = SLL_getNode( list, i );

printf("list[%d] : %d\n", i, current->data;);

}

   

// 모든 노드 제거

for( i = 0 ; i < count ; i++ )

{

SLL_removeNode( &list, current ) ;

SLL_destroyNode( current ) ;

}

}

링크드 리스트의 뒷이야기

단점

  • 다음 노드를 가리키려는 포인터 때문에 노드마다 4byte 메모리 추가가 필요
  • 특정 위치에 있는 노드를 얻는데 드는 비용이 크며 속도가 느림. 노드이 개수가 n이라면 최악의 경우 n회의 노드 탐색 루프를 실행해야함. 반면 배열은 상수 시간에 노드를 얻을 수 있음

장점

  • 새로운 노드의 추가/삽입/삭제가 쉽고 빠름, 배열은 삽입,삭제가 어려움
  • 현재 노드의 다음 노드를 얻어 오는 연산에 대해서는 비용이 발생하지 않음

링크드 리스트 정리

  • 노드의 추가/삽입/삭제는 빠르고, 찾기 연산은 느림
  • 더블 링크드 리스트

    더블 링크드 리스트를 소개합니다

    링크드 리스트의 탐색 기능을 개선한 자료구조

    양방향 탐색이 가능한 구조

    typedef struct tagNode

    {

    int Data; /* 데이터 필드 */

    struct tagNode* PrevNode ; /* 이전 노드를 가리키는 포인터 */

    struct tagNode* NextNode ; /* 다음 노드를 가리키는 포인터 */

    } Node ;

    더블 링크드 리스트의 주요 연산

    • 노드 생성/소멸

      싱글 링크드 리스트와 같음

      /* 노드생성 */

      Node* DLL_CreateNode(El ementType NewData)

      {

      Node* NewNode (Node*)malloc (sizeof (Node ));

      NewNode->Data NewData ;

      NewNode- >PrevNode NULL ;

      NewNode- >NextNode NULL;

      return NewNode ;

      }

      // 노드 소멸

      void DLL_DestroyNode( Node* node )

      {

      free(node);

      }

    • 노드 추가

      prev와 next를 연결

      //< node 삽입

      void DLL_appendNode( NODE** head, NODE* newNode )

      {

      //< head가 NULL이면 head 에 삽입

      if( (*head) == NULL )

      {

      *head = newNode ;

      }

      //< head가 NULL이 아니면 tail을 찾아 tail->next에 삽입

      else

      {

      NODE* tail = (*head);

      while( tail->next != NULL )

      {

      tail = tail->next ;

      }

         

      tail->next = newNode ;

      newNode->prev = tail;

      }

      }

    • 노드 탐색

      // 노드 탐색

      NODE* DLL_getNode( NODE* head, int location )

      {

      NODE* current = head ;

         

      while( current != NULL && (--location) >= 0 )

      {

      current = current->next ;

      }

         

      return current ;

      }

    • 노드 삭제

      //< node 삭제

      void DLL_removeNode( NODE** head, NODE* remove )

      {

      if ( (*head) == remove )

      {

      *head = remove->next ;

      if( (*head) != NULL )

      {

      (*head)->prev = NULL ;

      }

      }

      else

      {

      NODE* temp = remove ;

         

      if( remove->prev != NULL )

      {

      remove->prev->next = temp->next ;

      }

         

      if( remove->next != NULL )

      {

      remove->next->prev = temp->prev ;

      }

      }

         

      remove->prev = NULL;

      remove->next = NULL;

      }

    • 노드 삽입

      //< 노드를 찾아 삽입

      void DLL_insertAfter( NODE* current, NODE* newNode )

      {

      newNode->next = current->next ;

      newNode->prev = current ;

         

      if( current->next != NULL )

      {

      current->next->prev = newNode ;

      }

      current->next = newNode ;

      }

    • 노드 개수 세기

      // 노드 개수 세기

      int DLL_getNodeCount( NODE* head )

      {

      int count = 0 ;

      NODE* current = head ;

         

      while( current != NULL )

      {

      current = current->next ;

      count++ ;

      }

      return count ;

      }

      더블 링크드 리스트 예제 프로그램

    • 예제 DoubleLinkedList.h

#ifndef _DOUBLE_LINKED_LIST_H__

#define _DOUBLE_LINKED_LIST_H__

   

#include <stdio.h>

#include <stdlib.h>

   

typedef struct tagNode

{

int data ;

tagNode* prev ;

tagNode* next ;

}NODE ;

   

//< node 생성

NODE* DLL_createNode( int newData );

//< node 소멸

void DLL_destroyNode( NODE* node ) ;

//< node 삽입

void DLL_appendNode( NODE** head, NODE* newNode ) ;

void DLL_insertAfter( NODE* current, NODE* newNode ) ;

//< node 삭제

void DLL_removeNode( NODE** head, NODE* remove ) ;

//

NODE* DLL_getNode( NODE* head, int location ) ;

//

int DLL_getNodeCount( NODE* head ) ;

   

#endif

  • 예제 DoubleLinkedList.c

#include "stdafx.h"

#include "DLL.h"

   

//< node 생성

NODE* DLL_createNode( int newData )

{

NODE* newNode =(NODE*)malloc(sizeof(NODE));

   

newNode->next = NULL ;

newNode->prev = NULL ;

newNode->data = newData ;

   

return newNode ;

}

//< node 소멸

void DLL_destroyNode( NODE* node )

{

free(node);

}

//< node 삽입

void DLL_appendNode( NODE** head, NODE* newNode )

{

//< head가 NULL이면 head 에 삽입

if( (*head) == NULL )

{

*head = newNode ;

}

//< head가 NULL이 아니면 tail을 찾아 tail->next에 삽입

else

{

NODE* tail = (*head);

while( tail->next != NULL )

{

tail = tail->next ;

}

   

tail->next = newNode ;

newNode->prev = tail;

}

}

//< 노드를 찾아 삽입

void DLL_insertAfter( NODE* current, NODE* newNode )

{

newNode->next = current->next ;

newNode->prev = current ;

   

if( current->next != NULL )

{

current->next->prev = newNode ;

}

current->next = newNode ;

}

   

//< node 삭제

void DLL_removeNode( NODE** head, NODE* remove )

{

if ( (*head) == remove )

{

*head = remove->next ;

if( (*head) != NULL )

{

(*head)->prev = NULL ;

}

}

else

{

NODE* temp = remove ;

   

if( remove->prev != NULL )

{

remove->prev->next = temp->next ;

}

   

if( remove->next != NULL )

{

remove->next->prev = temp->prev ;

}

}

   

remove->prev = NULL;

remove->next = NULL;

}

   

// 노드 탐색

NODE* DLL_getNode( NODE* head, int location )

{

NODE* current = head ;

   

while( current != NULL && (--location) >= 0 )

{

current = current->next ;

}

   

return current ;

}

// 노드 개수 세기

int DLL_getNodeCount( NODE* head )

{

int count = 0 ;

NODE* current = head ;

   

while( current != NULL )

{

current = current->next ;

count++ ;

}

return count ;

}

  • 예제 main

// 02_DoubleLinkedList.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.

//

   

#include "stdafx.h"

#include "DLL.h"

   

int _tmain(int argc, _TCHAR* argv[])

{

int i = 0 ;

int count = 0 ;

NODE* list = NULL ;

NODE* newNode = NULL ;

NODE* current = NULL ;

   

// 추가

for ( i = 0 ; i < 5 ; i++ )

{

newNode = DLL_createNode(i);

DLL_appendNode(&list, newNode ) ;

}

   

// 출력

count = DLL_getNodeCount(list);

for( i = 0 ; i < count ; i++ )

{

current = DLL_getNode( list, i );

printf("list[%d] : %d\n", i, current->data ) ;

}

   

// 리스트의 3번째 노드 뒤에 추가

current = DLL_getNode( list, 2 ) ;

newNode = DLL_createNode( 3000 ) ;

DLL_insertAfter( current, newNode ) ;

   

// 출력

count = DLL_getNodeCount(list);

for( i = 0 ; i < count ; i++ )

{

current = DLL_getNode( list, i );

printf("list[%d] : %d\n", i, current->data ) ;

}

   

// 모든 노드 제거

count = DLL_getNodeCount( list ) ;

for ( i = 0 ; i < count ; i++ )

{

current = DLL_getNode( list, 0 ) ;

if ( current != NULL )

{

DLL_removeNode( &list, current ) ;

DLL_destroyNode( current ) ;

}

}

return 0;

}

   

  • 환형 링크드 리스트

    리스트 세계와 우로보로스, 환형 링크드 리스트

    머리가 꼬리를 물고 있는 형태의 링크드 리스트

    장점

    시작을 알면 끝을 알 수 있고, 끝을 알면 시작을 알 수 있음

    테일에 접근하는 비용이 거의 없음

    테일에 추가하는 노드에 대해서 성능 개선

    환형 더블 링크드 리스트의 주요 연산

    • 노드 추가

      // 노드 추가

      void CDLL_AppendNode(NODE** head, NODE* newNode)

      {

      // 헤드 노드가 NULL이면 새로운 새로

      if( (*head) == NULL )

      {

      *head = newNode;

      (*head)->next = *head;

      (*head)->prev = *head;

      }

      else

      {

      // 테일과 헤드 사이에 newNode 삽입

      NODE* tail = (*head)->prev;

         

      tail->next->prev = newNode;

      tail->next = newNode;

         

      newNode->next = (*head);

      newNode->prev = tail;

      }

      }

    • 노드 삭제

      // 노드 제거

      void CDLL_removeNode(NODE** head, NODE* remove)

      {

      if( *head == remove )

      {

      (*head)->prev->next = remove->next;

      (*head)->next->prev = remove->prev;

         

      *head = remove->next;

         

      remove->prev = NULL;

      remove->next = NULL;

      }

      else

      {

      NODE* temp = remove;

         

      remove->prev->next = temp->next;

      remove->next->prev = temp->prev;

         

      remove->prev = NULL;

      remove->next = NULL;

      }

      }

      환형 더블 링크드 리스트 예제 프로그램

    • 예제 CircularLinkedList.h

#ifndef CIRCULARLINKEDLIST_H__

#define CIRCULARLINKEDLIST_H__

   

#include <iostream>

   

   

typedef struct tagNode

{

int data ;

tagNode* prev ;

tagNode* next ;

}NODE ;

   

//< node 생성

NODE* CDLL_createNode( int newData );

//< node 소멸

void CDLL_destroyNode( NODE* node ) ;

// 삽입

void CDLL_appendNode( NODE** head, NODE* newNode );

// 노드 삽입

void CDLL_insertAfter(NODE* current, NODE* newNode);

// 삭제

void CDLL_removeNode( NODE** head, NODE* remove ) ;

//

NODE* CDLL_getNode( NODE* head, int location ) ;

//

int CDLL_getNodeCount( NODE* head ) ;

   

void printNode(NODE* node);

   

#endif

  • 예제 CircularLinkedList.c

#include "CircularLinkedList.h"

   

//< node 생성

NODE* CDLL_createNode( int newData )

{

NODE* newNode =(NODE*)malloc(sizeof(NODE));

   

newNode->next = NULL ;

newNode->prev = NULL ;

newNode->data = newData ;

   

return newNode ;

}

//< node 소멸

void CDLL_destroyNode( NODE* node )

{

free(node);

}

   

// 노드 추가

void CDLL_appendNode( NODE** head, NODE* newNode )

{

// 헤드가 NULL이면 새로운 노드가 head

if ( (*head) == NULL )

{

*head = newNode;

(*head)->next = *head;

(*head)->prev = *head;

}

else

{

// 테일과 헤드 사이에 newNode 삽입

                NODE* tail = (*head)->prev;

   

tail->next->prev = newNode;

tail->next = newNode;

   

newNode->next = (*head);

newNode->prev = tail;

}

}

void CDLL_insertAfter(NODE* current, NODE* newNode)

{

newNode->next = current->next;

newNode->prev = current;

   

current->next->prev = newNode;

current->next = newNode;

}

   

void CDLL_removeNode( NODE** head, NODE* remove )

{

if( *head == remove )

{

(*head)->prev->next = remove->next ;

(*head)->next->prev = remove->prev ;

   

*head = remove->next;

   

remove->prev = NULL ;

remove->next = NULL ;

}

else

{

NODE* temp = remove;

   

remove->prev->next = temp->next;

remove->next->prev = temp->prev;

   

remove->prev = NULL;

remove->next = NULL;

}

}

   

// 노드 탐색

NODE* CDLL_getNode( NODE* head, int location )

{

NODE* current = head ;

   

while( current != NULL && (--location) >= 0 )

{

current = current->next ;

}

   

return current ;

}

// 노드 개수 세기

int CDLL_getNodeCount( NODE* head )

{

int count = 0 ;

NODE* current = head ;

   

while( current != NULL )

{

current = current->next ;

count++ ;

   

if( current == head )

{

break;

}

}

return count ;

}

   

void printNode(NODE* node)

{

if( node->prev == NULL )

{

printf("prev : NULL");

}

else

{

printf("prev : %d", node->prev->data);

}

printf("current : %d", node->data);

   

if( node->next == NULL )

{

printf("next : NULL\n");

}

else

{

printf("next : %d\n", node->next->data);

}

}

  • 예제 main.c

#include "CircularLinkedList.h"

   

void main(void)

{

int i = 0;

int count = 0;

NODE* list = NULL;

NODE* newNode = NULL ;

NODE* current = NULL ;

   

// 추가

for ( i = 0 ; i < 5 ; i++ )

{

newNode = CDLL_createNode(i);

CDLL_appendNode( &list, newNode ) ;

}

   

// 출력

count = CDLL_getNodeCount( list ) ;

for ( i = 0 ; i < count ; i++ )

{

current = CDLL_getNode(list, i);

printf("list[%d] : %d\n", i, current->data);

}

   

// 리스트의 세 번째 칸 뒤에 노드 삽입

printf("\nInserting 3000after[2]..\n\n");

current = CDLL_getNode(list, 2);

newNode = CDLL_createNode(3000);

CDLL_insertAfter(current, newNode);

   

// 리스트 출력

// 노드 수의 2배만큼 루프를 돌며 환영임을 확인

count = CDLL_getNodeCount(list);

for( i = 0 ; i < count*2 ; i++ )

{

if( i == 0 )

current = list;

else

current = current->next;

   

printf("list[%d] : %d\n", i, current->data);

}

   

// 모든 노드 메모리에서 제거

printf("\nDestroing List\n");

count = CDLL_getNodeCount(list);

   

for( i = 0 ; i < count ; i++ )

{

current = CDLL_getNode(list, 0);

if( current != NULL )

{

CDLL_removeNode(&list, current);

CDLL_destroyNode(current);

}

}

}

  

반응형

'책정리 > 뇌를 자극하는 알고리즘' 카테고리의 다른 글

5장 정렬  (0) 2015.03.23
4장 트리  (0) 2015.03.22
3장 큐  (0) 2015.03.19
2장 스택  (0) 2015.03.19
목차  (0) 2015.03.18