연결구조

연결리스트(linked list)

순차 자료구조에서의 연산시간과 저장공간에 대한 문제를 개선하여 자료를 표현한 방법

자료의 논리적인 순서와 물리적인 순서가 일치하지 않는 자료구조를 말함

  • 각 원소에 저장되어 있는 다음 원소의 주소에 의해 순서가 연결되는 방식임
    • 비순차적 저장(기억장소의 어느 곳에서든지)이 가능함
  • 물리적인 순서를 맞추기 위한 오버헤드가 발생하지 않음
  • 여러 개의 작은 공간을 연결하여 하나의 전체 자료구조를 표현함
  • 크기의 변경이 유연하고 더 효율적으로 메모리를 사용할 수 있음
  • 순차적인 기억장소의 표현에서 발생하는 이동에 따른 연산 소요시간을 해결할 수 있음

배열 구조와 연결리스트 구조의 표현

순차 자료구조의 배열과 연결리스트 표현의 특징

배열구조 - 계산주소방법에 의해 배열 원소의 위치를 파악할 수 있음
- 순차적인 원소간의 관계에 대한 정보가 필요 없음
연결리스트
구조
- 순차 리스트의 원소를 찾기 위해 다음 원소에 대한 정보가 필요함(포인터 변수 이용, 추가기억공간을 요구)
- 배열(임의처리, 직접처리)과 달리 임의의 원소를 처리하지 못하기 때문에 처음부터 연결을 따라 추적해야 함(시간소요)
- 포인터를 사용함으로써 노드의 삽입/삭제가 용이함
- 연속적인 기억공간이 없어도 저장이 가능함
- 알고리즘 구현(프로그램 복잡성)이 복잡함
- 노드 탐색 시 무한반복에 빠질 수 있음



연결리스트의 표현

연결리스트 노드의 구조

데이터와 연결(link) 정보를 저장하기 위한 기억공간이 필요함

  • 데이터 값 - 항목이 n개인 데이터 필드(1 < i < n)
    • 원소의 값을 저장 - 저장할 원소의 형태에 따라서 하나 이상의 필드로 구성할 수 있음
  • 연결 값 - 항목이 n개의 연결 필드(1 < i < n)
    • 다음 노드의 주소를 저장 - 포인터 변수를 사용하여 주소 값을 저장, 하나 이상의 연결로 구성할 수 있음
  • 노드의 논리적 표현 스크린샷 2023-10-02 160640
  • 연결리스트 구조의 논리적 표현 스크린샷 2023-10-02 160737



동적 기억장소의 할당/해제

동적 기억장소의 할당

동적 기억장소 할당 함수 - malloc()

배열의 문제점을 해결

  • 각 자료 원소는 서로 인접해 있지 않고 기억장소 내에서 어디든지 흩어져 존재함
  • 삽입/삭제 시 원소 이동이 없음
  • 포인터를 이용하여 노드를 동적으로 생성하므로 기억장소를 미리 확보할 필요 없음
  • 기억장소의 크기는 일정한 크기로 제한되어 있기 때문에 malloc() 함수를 사용하여 동적 기억장소를 무한히 할당하여 사용하지는 못함

함수 원형 - void *malloc(size_tsize);

  • 동적 기억장소로 사용되는 힙(heap)으로부터 원하는 크기의 바이트를 할당 해주는 함수()
  • 정상적으로 기억장소를 할당하는 경우에는 기억장소의 주소를 반환하고 비정상적인 경우에는 NULL을 반환함
int *pt;
pt = (int*) malloc(10); 
pt = (int*) malloc(15*sizeof(int)); // 15개의 int를 위한 기억 장소를
                                    // 할당해 그 시작 주소값을 반환함


동적 기억장소의 해제

동적 기억장소의 해제 함수 - free()

함수 원형 - void free(void*)

  • 할당된 기억장소를 해제하여 힙(heap)에 반환함()

동적 기억장소의 할당과 해제의 구현

malloc()와 free()함수를 사용한 프로그램

#include <stdio.h>
#include <stdlib.h>
void main(){
    int* memory;
    memory = (int*)malloc(sizeof(int)*5); //할당된 기억공간의 주소 값을 저장
    memory[0] = 10;
    memory[1] = 20;
    memory[2] = 30;
    memory[3] = 40;
    memory[4] = 50;
    int i = 0;
    for (i = 0; i < 5; i++)
        printf("memory[%d] : %d\n", i, memory[i]);
    free(memory);
}

실행결과

memory[0] : 10
memory[1] : 20
memory[2] : 30
memory[3] : 40
memory[4] : 50


자유 공간리스트(free space list)

사용하기 전의 메모리나 사용이 끝난 메모리를 관리하기 위해 노드로 구성하여 연결한 리스트(리스트가 다 소모되어 없을 때는 malloc()을 사용)를 말함

자유 공간리스트를 사용하기 위해서는 자유 공간리스트로부터 새로운 공백 노드를 할당받고, 그 메모리 주소에 대한 참조값을 반환하는 getNode() 함수가 필요하다.

노드의 할당과 반환 알고리즘

getNode(){ //노드의 할당
    if (free == NULL) underflow; //언더플로우 처리
    new = free; //ⓐ
    free = free->link; //ⓑ
    return new; //ⓒ
}
returnNode() { //노드의 반환
    old->link = free; //ⓐ
    free = old; //ⓑ
}



단일 연결리스트(singly linked list)

연결리스트 자료구조의 종류

연결의 표현 방법에 의해 분류

  • 단일 연결리스트
  • 환형 연결리스트
  • 이중 연결리스트
  • 이중 환형 연결리스트

단일 연결리스트의 구조

노드가 하나의 연결 필드에 의해서 다음 노드와 연결되는 구조를 가진 연결리스트를 의미함

  • 노드 구조 - 데이터 필드와 1개의 연결 필드로 구성됨
  • 자료 탐색은 한 방향으로만 진행

노드의 구현

구조체의 멤버 접근 연산자 프로그램

#include <stdio.h>
#include <stdlib.h>
struct list_node{//구조체 정의
    int i;
    struct list_node *link;
};

int main(){
    struct list_node node1; //구조체 변수 선언 - 메모리가 할당됨
    struct list_node *node2=malloc
    (sizeof(struct list_node)); //구조체
                                //포인터에 메모리 할당
    struct list_node *node3=malloc(sizeof(struct list_node));

    node1.i=100;
    node2->i=200;
    node2->link=&node1;
    node3->link=&node1;

    printf("node1의 i값 = %d\n", node1.i); 
    printf("node1의 주소값= %0x\n", &node1); 
    printf("node2의 주소값= %0x\n", &node2); 
    printf("node2의 i값 = %d\n", node2->i); //주소를 통해 그 값에 접근하는 것을
    // 역참조(dereference)라함
    printf("node2의 i값 = %d\n", (*node2).i); //node2->i와 같음
    printf("node3의 link값= %0x\n", (*node3).link);
    return 0;
}

실행결과

node1의 i값 = 100
node1의 주소값= 62fe00
node2의 주소값= 62fdf8
node2의 i값 = 200
node2의 i값 = 200
node3의 link값= 62fe00


단일 연결리스트의 노드 탐색

노드의 삽입/삭제 시 특정 노드를 찾고자 할 때 리스트의 노드를 처음부터 하나식 순회하면서 노드의 데이터 필드 값과 x(찾고자 하는 노드)를 비교하여 일치하는 노드를 찾을 필요가 있음

노드의 생성과 출력 구현

단일 연결리스트의 생성과 노드들의 출력 프로그램

#include <stdio.h>
#include <stdlib.h>
struct node{
    int num; //데이터 영역
    struct node *nextptr; //연결 영역
}*stnode;

void createNodeList(int n){ //연결리스트의 생성
    struct node *fnNode, *tmp;
    int num, i;
    stnode = (struct node*)malloc(sizeof(struct node));
    if(stnode == NULL){ //stnode가 NULL인지 검사
        printf("메모리가 할당될 수 없음.");
    }
    else{ //노드의 데이터 입력
        printf("1번째 노드의 데이터 입력:");
        scanf("%d",&num);
        stnode->num = num;
        stnode->nextptr = NULL;//연결을 NULL로 할당
        tmp = stnode;
        for(i=2; i<=n; i++){ //n개의 노드를 생성하여 리스트에 추가
            fnNode = (struct node*)malloc(sizeof(struct node));
            if(fnNode == NULL){//새로운 공간을 할당
                printf("메모리가 할당될 수 없음.");
                break;
            }
            else{
                printf("%d n번째 노드의 데이터 입력:",i);
                scanf("%d", &num);
                fnNode->num = num;
                fnNode->nextptr = NULL;
                tmp->nextptr = fnNode;
                tmp = tmp->nextptr;
            }
        }
    }
}

void displayList(){
    struct node *tmp;
    if(stnode == NULL){
        printf("리스트가 비었음.");
    }
    else{
        tmp = stnode;
        while(tmp != NULL){
            printf("데이터 = %d\n", tmp->num); //현재 노드의 데이터 출력
            tmp = tmp->nextptr; //현재 노드의 위치를 다음 노드로 변경
        }
    }
}

int main(){
    int n;
    printf("\n\단일 연결리스트 생성/출력\n");
    printf("------------------------\n");
    printf("노드의 수를 입력:");
    scanf("%d",&n);
    createNodeList(n);
    printf("\n리스트의 데이터를 출력:\n");
    displayList();
    return 0;
}

실행결과

단일 연결리스트 생성/출력
------------------------------
노드의 수를 입력: 3 
1번째 노드의 데이터 입력: 10 
2번째 노드의 데이터 입력: 20 
3번째 노드의 데이터 입력: 30
리스트의 데이터를 출력:
데이터 = 10
데이터 = 20
데이터 = 30


노드의 삽입/삭제 구현

단일 연결리스트의 삽입/삭제 프로그램

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

struct Node{//노드의 생성
    int item;
    struct Node* next;
}

void insertAtBeginning(struct Node** ref, int data){// 리스트 처음에 삽입
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    new_node->item = data;//데이터 삽입
    new_node->next = (*ref);
    (*ref) = new_node;//새로운 노드를 헤드로 연결
}

void insertAfter(struct Node* node, int data){//중간에 삽입
    if(node == NULL){
        printf("이전의 노드는 NULL이 될 수 없음.");
        return;
    }

    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    new_node->item = data;
    new_node->next = node->next;
    node->next = new_node;
}

void insertAtEnd(struct Node** ref, int data){//마지막에 삽입
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    struct Node* last = *ref;
    new_node->item = data;
    new_node->next = NULL;
    if(*ref == NULL){
        *ref = new_node;
        return;
    }
    while(last->next != NULL)//마지막 원소까지 이동
        last = last->next;
    last->next = new_node;
    return;
}

void deleteNode(struct Node** ref, int key){//노드의 삭제
    struct Node *temp = *ref, *prev;
    if(temp != NULL && temp->item == key){
        *ref = temp->next;
        free(temp);
        return;
    }
    while(temp != NULL && temp->item != key){//삭제될 원소를 찾음
        prev = temp;
        temp = temp->next;
    }
    if(temp == NULL) return; //찾는 데이터가 없으면
    prev->next = temp->next; // 해당 노드를 삭제
    free(temp);
}

void printList(struct Node* node){//노드 출력
    while (node != NULL){
        printf(" %d ", node->item);
        node = node->next; 
    }
}

int main(){
   struct Node* head = NULL;
    insertAtEnd(&head, 1);
    insertAtBeginning(&head, 2); insertAtBeginning(&head, 3);
    insertAtEnd(&head, 4); insertAfter(head->next, 5);
    printf("연결리스트: ");
    printList(head);
    printf("\n삭제 이후의 연결리스트: ");
    deleteNode(&head, 4);
    printList(head); 
}

실행결과

연결리스트: 3 2 5 1 4
삭제 이후의 연결리스트: 3 2 5 1