자료구조

리스트(List), 연결 리스트(Linked List)

귤먹는코더 2025. 2. 13. 19:53
728x90

리스트란?

- 리스트는 배열 기반 자료구조로, JavaScript에서 Array 객체를 통해 사용된다.

 

🔹 리스트 주요 연산

const list = [10, 20, 30, 40, 50];

// 조회 (O(1))
console.log(list[2]); // 30

// 삽입 (끝: O(1), 앞/중간: O(n))
list.push(60);  // [10, 20, 30, 40, 50, 60]
list.unshift(0); // [0, 10, 20, 30, 40, 50, 60]

// 삭제 (끝: O(1), 앞/중간: O(n))
list.pop();   // [0, 10, 20, 30, 40, 50]
list.shift(); // [10, 20, 30, 40, 50]

// 탐색 (O(n))
console.log(list.indexOf(30)); // 2
console.log(list.includes(50)); // true

 

🔹 리스트를 활용한 코딩 테스트 예제

배열을 거꾸로 뒤집기

function reverseArray(arr) {
    return arr.reverse();
}
console.log(reverseArray([1, 2, 3, 4, 5])); // [5, 4, 3, 2, 1]

 

연속된 숫자의 누적 합 구하기 (Prefix Sum)

function prefixSum(arr) {
    const result = [arr[0]];
    for (let i = 1; i < arr.length; i++) {
        result.push(result[i - 1] + arr[i]);
    }
    return result;
}
console.log(prefixSum([1, 2, 3, 4])); // [1, 3, 6, 10]

 

슬라이딩 윈도우 (고정된 길이의 최대 합 구하기)

function maxSumSubarray(arr, k) {
    let maxSum = 0, windowSum = 0;
    for (let i = 0; i < k; i++) {
        windowSum += arr[i];
    }
    maxSum = windowSum;

    for (let i = k; i < arr.length; i++) {
        windowSum += arr[i] - arr[i - k];
        maxSum = Math.max(maxSum, windowSum);
    }
    return maxSum;
}

console.log(maxSumSubarray([2, 1, 5, 1, 3, 2], 3)); // 9

 

연결 리스트란?

- 연결 리스트는 노드(Node)와 포인터(참조)로 이루어진 자료구조이다.

 

🔹 단일 연결 리스트(Singly Linked List)

- 각 노드가 값과 다음 노드를 가리키는 포인터를 포함합니다.

class ListNode {
    constructor(value) {
        this.value = value;
        this.next = null;
    }
}

class LinkedList {
    constructor() {
        this.head = null;
    }

    // 리스트 끝에 값 추가 (O(n))
    append(value) {
        const newNode = new ListNode(value);
        if (!this.head) {
            this.head = newNode;
            return;
        }
        let current = this.head;
        while (current.next) {
            current = current.next;
        }
        current.next = newNode;
    }

    // 리스트 출력
    print() {
        let current = this.head;
        const values = [];
        while (current) {
            values.push(current.value);
            current = current.next;
        }
        console.log(values.join(" -> "));
    }
}

// 연결 리스트 사용 예제
const list = new LinkedList();
list.append(10);
list.append(20);
list.append(30);
list.print(); // 10 -> 20 -> 30

 

🔹 연결 리스트를 활용한 코딩 테스트 예제

연결 리스트 뒤집기

function reverseLinkedList(head) {
    let prev = null;
    let current = head;

    while (current) {
        let next = current.next;
        current.next = prev;
        prev = current;
        current = next;
    }

    return prev;
}

// 연결 리스트 생성
class ListNode {
    constructor(value) {
        this.value = value;
        this.next = null;
    }
}

// 리스트 출력 함수
function printList(head) {
    let current = head;
    const values = [];
    while (current) {
        values.push(current.value);
        current = current.next;
    }
    console.log(values.join(" -> "));
}

// 예제 실행
const head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);

console.log("원래 리스트:");
printList(head); // 1 -> 2 -> 3 -> 4

const reversedHead = reverseLinkedList(head);
console.log("뒤집힌 리스트:");
printList(reversedHead); // 4 -> 3 -> 2 -> 1

 

두 개의 정렬된 연결 리스트 합치기

function mergeTwoLists(l1, l2) {
    if (!l1) return l2;
    if (!l2) return l1;

    if (l1.value < l2.value) {
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    } else {
        l2.next = mergeTwoLists(l1, l2.next);
        return l2;
    }
}

// 예제 실행
const l1 = new ListNode(1);
l1.next = new ListNode(3);
l1.next.next = new ListNode(5);

const l2 = new ListNode(2);
l2.next = new ListNode(4);
l2.next.next = new ListNode(6);

console.log("리스트 1:");
printList(l1); // 1 -> 3 -> 5

console.log("리스트 2:");
printList(l2); // 2 -> 4 -> 6

const mergedList = mergeTwoLists(l1, l2);
console.log("합쳐진 리스트:");
printList(mergedList); // 1 -> 2 -> 3 -> 4 -> 5 -> 6

 

연결 리스트에서 중간 노드 찾기 (투 포인터 활용)

function findMiddleNode(head) {
    let slow = head, fast = head;

    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
    }

    return slow;
}

// 예제 실행
const list = new ListNode(10);
list.next = new ListNode(20);
list.next.next = new ListNode(30);
list.next.next.next = new ListNode(40);
list.next.next.next.next = new ListNode(50);

console.log("주어진 리스트:");
printList(list); // 10 -> 20 -> 30 -> 40 -> 50

const middleNode = findMiddleNode(list);
console.log("중간 노드 값:", middleNode.value); // 30

 

리스트(List) vs 연결 리스트(Linked List)

비교 리스트 (배열 기반) 연결 리스트
메모리 구조 연속된 메모리 공간 노드들이 포인터로 연결됨
접근 속도 O(1) (인덱스로 접근 가능) O(n) (순차 탐색 필요)
삽입/삭제 속도 O(n) (중간 삽입/삭제 시 요소 이동 필요) O(1) (포인터 변경으로 해결 가능)
메모리 사용량 데이터만 저장 추가로 포인터 저장 공간 필요
정렬 기본 제공 (sort()) 직접 구현 필요

 

리스트와 연결 리스트의 활용 패턴 정리

문제 유형 배열 활용 연결 리스트 활용
인덱스로 접근 O(1) O(n)
삽입/삭제 O(n) (중간 요소 이동 필요) O(1) (포인터 변경)
정렬이 필요한 문제 sort() 사용 가능 직접 구현 필요
연속적인 데이터 활용 슬라이딩 윈도우, 누적 합 느린 접근 속도로 부적합
데이터 삽입/삭제 빈번한 문제 splice() 사용 가능하지만 성능 저하 효율적인 삽입/삭제 가능
노드 기반 문제 (ex: LRU 캐시, 그래프 연결) 부적합 최적

 

결론

- 배열은 고정된 크기의 데이터를 다룰 때 유리하며, 빠른 접근이 필요한 문제에 적합합니다.

- 연결 리스트는 삽입/삭제가 빈번한 문제에서 성능이 뛰어나며, 노드 기반 문제(LRU, 그래프 탐색)에서 활용됩니다.

728x90

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

해시(Hash)  (0) 2025.02.26
데큐(Deque)  (0) 2025.02.18
큐(Queue)  (0) 2025.02.17
스택(stack)  (0) 2025.02.15
배열  (0) 2025.02.12