연결리스트

  • 연결리스트란?
    • 노드라고 불리는 개체들이 포인터를 사용해 서로 연결된 자료구조
    • 배열과 다르게 연속된 메모리 공간을 사용하지 않으며, 동적으로 크기 조절 가능
  • 종류
    • 단일 연결리스트
      • 한 방향으로 연결된 연결리스트
      • 각 노드는 다음 노드를 가르키지만, 이전 노드를 가르키는 포인터 없음
      • 마지막 노드의 next 가 nullptr
      • 삽입/삭제의 시간 복잡도는 O(1)(앞)/O(N)(중간)
      • 검색, 접근의 시간 복잡도는 O(N)
    • 이중 연결리스트
      • 양방향으로 연결된 리스트
      • 각 노드는 next와 prev를 가짐
      • 삽입/삭제의 시간 복잡도는 O(1)
      • 검색, 접근의 시간 복잡도 O(N)
      • 장점
        • 앞뒤로 이동이 가능해 탐색 속도가 빠름
        • 노드 삭제시, 앞 뒤 노드를 몰라도 가능
      • 단점
        • prev 포인터를 유지해야해 메모리 사용량 증가
        • 삽입/삭제시 포인터를 2개씩 조작해야해 복잡도 증가
    • 원형 연결리스트
      • 마지막 노드가 첫번째 노드를 가리켜 순환하는 구조
      • 장점
        • 끝에서 다시 처음으로 돌아갈 수 있어 순환하는 작업에 유리
        • 특정 노드에서 출발하면 모든 노드 방문 가능
      • 단점
        • 끝이 어디인지 구분 어려움
        • 무한 루프에 빠질 위험 있음

배열과 연결 리스트

  • 가변 배열(vector) 
    • 연속된 메모리 공간 사용
    • 저장 공간이 부족하면 메모리를 새로 잡아 사용하면서 저장하는데, 이때 남는 메모리가 존재하므로 메모리 단편화 일어남
      • 메모리 단편화란?
        • 메모리에 빈 공간 또는 자료가 여러개의 조각으로 나뉘는 현상
        • 실제 공간이 남아있어도 할당이 불가능한 상태
    • 삽입 삭제가 느리기 때문에 삽입 삭제가 빈번할 때 비효율적
    • 주소가 연속되어 있고, index로 접근하기 때문에 검색이 빠름
    • ex)
      • 게임에서 몬스터는 생성 및 삭제가 빈번하지만 기획적으로 수가 변경될 수 있기 때문에 원하는 만큼 몬스터를 배열로 만든 뒤, 죽거나 필요 없어지면 안보이도록 처리, 다시 생성되야할 땐 hp등 내부의 값을 변경한 뒤 생성될 위치로 옮겨 보이도록 처리
      • 메모리 풀링을 이용한 방식임
        • 메모리 풀링 : 메모리를 미리 할당해두고 가져다 쓰는 방법으로 메모라 단편화를 막음
        • 할당해 놓은채 사용하지 않는 메모리 발생시 메모리 누수 발생
        • 메모리 할당, 해제가 빈번한 곳에서 사용
  • list
    • 메모리 단편화가 발생할 소지가 적음
    • 메모리 단편화가 발생해도 용량이 적음
    • 삽입 삭제가 빠름
    • 주소를 타고 들어가야해서 검색이 느림
  • 배열과 연결리스트 차이
  연결리스트 배열
메모리 구조 비연속적(포인터로 연결) 연속적(배열과 동일)
메모리 할당 동적할당(노드 단위) 동적 할당(배열 단위, 연속적)
랜덤 접근 불가능 가능
시간 복잡도 O(N) O(1)
삽입/삭제(중간) O(1)(포인터 변경) O(N)(밀어야함)
삽입/삭제(끝) O(1) O(1)(여유공간 있을 때), O(N)(리사이징 발생시)
메모리 오버헤드 높음(포인터 추가 필요) 낮음(데이터만 저장)
캐시 효율성 낮음(노드 흩어짐) 높음(연속된 메모리)
배열 크기 조절 추가/삭제시 새로운 노드 할당 크기 초과 시 재할당 & 복사 필요

단일 연결 리스트 구현

#pragma once

#include<iostream>

template<typename T> 
class LinkedList
{
public:
    struct Node;//전방선언
public:
    LinkedList(Node* head)
    {
        this->head = head;
    }

    static Node* Create(T data)
    {
        Node* node = new Node();

        node->Data = data;
        node->Next = nullptr;
        return node;
    }
    static void Destroy(Node* node)
    {
        delete node;
        node = nullptr;
    }

    Node* Head(){return head;}

    void Push(Node* node)
    {
        Node* tail = head;
        while(tail->Next != nullptr)
            tail = tail->Next;
        tail->Next = node;
    }

    void Insert(Node* current, Node* node)
    {
        node->Next = current->Next;
        current->Next = node;
    }
    
    void Insert(int index, Node* node)
    {
        Node* temp = GetNode(index);

        Insert(temp, node);
    }

    Node* GetNode(int index)
    {
        Node* curr = head;

        while (curr != nullptr && --index >= 0)
            curr = curr->Next;
        
        return curr;
    }

    int GetNodeCount()
    {
        int count = 0;
        Node* current = head;
        while (current != nullptr)
        {
            current = current->Next;
            count++;
        }
        return count;
    }

    void Print(Node* node = nullptr)
    {
        if(node == nullptr)
            return;
        std::cout<<node->Data <<std::endl;
        Print(node->Next);
    }

public:
    struct Node
    {
        T Data;
        Node* Next;
    };

private:
    Node* head;
};

'자료구조 > 자료구조 정리' 카테고리의 다른 글

Stack  (0) 2025.04.23
이중 연결 리스트  (0) 2025.04.08
템플릿  (0) 2025.03.25
선형, 비선형 자료구조  (0) 2025.03.18