단일 연결 리스트가 단방향으로만 연결된 리스트였다면, 이중 연결 리스트는 양방향으로 연결된 리스트다.
위의 사진에서 확인할 수 있듯, next와 prev를 통해 노드들이 양방향적으로 연결되어 있다.
이러한 특성 때문에 조금 더 유연하고 효율적으로 연결 리스트를 활용할 수 있다. 하지만, 이중 연결 리스트는 prev라는 프로퍼티가 하나 더 추가되었기 때문에 메모리 차지 비중이 단일 연결 리스트에 비해 크다는 상대적 단점을 갖는다. 따라서 이중 연결 리스트를 활용할 때, 이러한 공간 복잡도를 잘 고려하여 활용해야 한다.
연결 리스트에 대한 기본적인 내용들은 앞선 포스팅에서 다뤘기에, 바로 활용 방법에 대한 설명으로 들어가보도록 하겠다.
활용 방법
주요 메소드는 앞 장에서 설명했던 것과 같다. (push, pop, get, set, insert, remove)
공통 클래스
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 노드 생성을 위한 클래스 classNode{ constructor(val) { this.val = val; this.next = null; this.prev = null; } }
// DLL 생성을 위한 클래스 뼈대 classDLL{ constructor() { this.head = null; this.tail = null; this.length = 0; } }
classDLL{ constructor() { this.head = null; this.tail = null; this.length = 0; } insert(index, val) { // 인덱스 범위 밖 X (하지만, 삽입을 원하는 위치가 가장 끝일 수도 있기 때문에 이번에는 index > this.length가 조건) if (index < 0 || index > this.length) returnnull; // index가 0이면 unshift와 동일하다 (맨 앞에 삽입을 하겠다는 것이기 때문) if (index === 0) returnthis.unshift(val); // 끝에 삽입하겠다는 것은 push와 동일하다. if (index === this.length) returnthis.push(val); const newNode = new Node(val); const leftNode = this.get(index - 1); const rightNode = leftNode.next; // 좌우 노드들과 삽입 노드를 연결지어 준다. newNode.next = rightNode; newNode.prev = leftNode; rightNode.prev = newNode; leftNode.next = newNode; this.length++; return link; } }
remove
원하는 노드를 삭제해주면 된다. 여기서도 동일하게 노드 간의 연결과 기존 연결의 제거를 신경써줘야 한다.
classDLL{ constructor() { this.head = null; this.tail = null; this.length = 0; } remove(index, val) { // 없는 노드를 삭제할 수는 없으니까! if (index < 0 || index >= this.length) returnnull; // 0번을 삭제 ? -> shift() if (index === 0) returnthis.shift(); // 끝을 삭제 ? -> pop() if (index === this.length - 1) returnthis.pop(); const targetNode = this.get(index); const leftNode = targetNode.prev; const rightNode = targetNode.next; // 삭제할 노드의 next prev는 제거해준다. targetNode.next = null; targetNode.prev = null; // 그리고 left와 right 노드를 서로 연결해준다. leftNode.next = rightNode; rightNode.prev = leftNode; this.length--; return link; } }
이중 연결 리스트의 시간 복잡도
메소드
시간 복잡도
Insertion (삽입)
O(1)
Removal (삭제)
O(1)
Search (탐색)
O(N)
Access (접근)
O(N)
단일 연결 리스트에서의 Insertion과 removal은 모두 O(N)의 시간복잡도를 가졌다.
하지만, 이중 연결 리스트에서는 두 메소드 모두 **O(1)**이라는 굉장히 효율적인 시간복잡도를 갖는다.
따라서, 삽입과 삭제가 잦은 경우에는 단일 연결 리스트보다 이중 연결 리스트를 활용하는 것이 효율적일 것이다. 반면, 이중 연결 리스트는 위에서도 설명했듯, 메모리 효율성이 단일 연결 리스트보다는 떨어지기에 이를 잘 고려하여 두 자료구조 중 하나를 선택하여 활용하는 것이 좋다.