데이터를 효율적으로 저장하고 관리하는 것은 프로그래밍의 기본과 기초이자 핵심 과제이다. 여러 자료구조 중에서도 배열(Array)과 연결 리스트(Linked List)는 기본적인 형태이며, 각각의 고유한 특성으로 인해 다양한 상황에서 활용된다.
배열의 한계와 연결 리스트의 등장
배열의 장점
- 빠른 참조: 특정 인덱스에 대한 읽기(Read) 또는 쓰기(Write)와 같은 참조 작업에는 O(1)의 뛰어난 성능을 보인다. 메모리에 연속적으로 할당되어 있기 때문에, 시작 주소만 알면 원하는 데이터의 위치를 수학적으로 계산(offset 계산)을 통해 즉시 파악할 수 있기 때문이다. 예를 들어, int[] arr = {1, 2, 3, 4, 5};에서 arr[3]으로 네 번째 데이터(인덱스 3)에 접근할 경우, 배열의 시작 주소에서 (3 * sizeof(int))만큼 떨어진 주소에 바로 접근하는 식이다
배열의 단점
- 연속된 메모리 공간 필요: 배열의 모든 요소는 메모리 상에 연속적으로 할당되어야 한다. 이는 대용량의 배열을 생성할 때, 시스템이 충분히 큰 연속된 메모리 블록을 찾지 못할 수 있다는 한계가 있다
- 고정된 크기: 초기 선언 시 배열의 크기를 지정해야 하며, 그 크기가 고정적이다. 초기에 배열의 크기를 정확히 알지 못하면, 너무 작게 선언할 경우 데이터 추가 시 재할당에 따른 오버헤드가 발생하거나, 너무 크게 선언할 경우 불필요한 메모리가 낭비될 수 있다
- 비효율적인 삽입/삭제: 배열의 중간에 데이터를 삽입하거나 삭제할 경우, 해당 위치 이후의 모든 데이터를 한 칸씩 뒤로 밀거나 당겨야 하므로 O(N)의 시간 복잡도를 가진다. 이는 데이터 양이 많아질수록 성능 저하의 주범이 된다
배열의 단점을 해결하기 위해 고민했고, 그 결과 연결 리스트라는 자료구조가 탄생했다. 연결 리스트는 데이터를 메모리 공간의 불연속적인 위치에 분산하여 할당하고, 이 데이터들을 서로 포인터(또는 참조)를 통해 연결해주는 방식을 사용한다
연결 리스트의 기본 구조 – 노드
연결 리스트의 핵심 구성 요소는 노드(Node)이다. 각 노드는 두 가지 주요 필드를 가진다
- 데이터 필드(Data Field): 실제 저장하고자 하는 데이터를 담는 변수이다
- 포인터 필드(Pointer Field / Next Field): 다음 노드의 주소(또는 참조)를 가리키는 변수이다. 리스트의 마지막 노드는 이 필드가 null을 가리키게 된다
데이터를 저장할 필요가 있을 때마다 새로운 노드를 생성하여 데이터를 저장하고, 기존 노드와 새로운 노드를 연결하는 방식으로 리스트를 구성한다. 이러한 연결 방식 때문에 ‘연결 리스트’라고 불리는 것이다. 연결 리스트는 첫 노드의 주소(이를 head라고 부른다)만 알고 있으면, 포인터를 따라가며 다른 모든 노드에 접근할 수 있다
+----+------+ +----+------+ +----+------+ | Data | Next |---->| Data | Next |---->| Data | Next |----> null +----+------+ +----+------+ +----+------+ (Head)
연결 리스트의 장점
- 동적인 크기 조절: 데이터를 추가할 때 빈 메모리 공간 어디든 노드를 생성하고 기존 노드와의 연결 관계만 재설정해주면 되므로, 배열처럼 초기 크기를 미리 지정해야 하는 제약이 없다. 필요에 따라 노드를 추가하거나 제거하여 유연하게 크기를 조절할 수 있다.
- 효율적인 삽입/삭제: 삽입하거나 삭제할 위치를 이미 알고 있는 상태에서 작업 자체만 본다면, 연결 리스트는 포인터(참조)만 변경하면 되므로 O(1)의 시간 복잡도를 가진다. 이는 배열이 데이터를 옮겨야 하므로 O(N)인 것과 대비되는 큰 장점이다
연결 리스트의 단점
- 느린 데이터 참조(접근): 데이터들이 메모리에 불연속적으로 분산되어 있기 때문에, 원하는 데이터를 바로 찾아갈 수 없다. 특정 인덱스(예: 네 번째 데이터)에 접근하고 싶다면, 첫 번째 노드(head)부터 시작하여 다음 노드를 가리키는 포인터를 따라 순차적으로 이동해야 한다. 즉, 연결 리스트에서 특정 인덱스에 대한 데이터 참조는 최악의 경우 O(N)의 성능을 가지며, 배열의 O(1)보다 훨씬 느리다.
- 메모리 오버헤드: 각 노드는 데이터 외에 다음 노드의 주소를 저장하는 포이넡 필드를 추가로 가지고 있어야 한다. 이 포인터가 차지하는 메모리는 순수 데이터 외의 추가적인 공간으로, 메모리 효율성 측면에서 배열보다 불리할 수 있다
- 캐시 효율성 저하: 노드들이 메모리에 불연속적으로 흩어져 있기 때문에, CPU 케시 메모리의 효율(캐시 지역성, Cache Locality)이 배열에 비해 떨어질 수 있다. 이는 데이터를 순차적으로 처리할 때 성능에 영항을 미친다
배열과 연결 리스트 비교
| 특징 | 배열(Array) | 연결 리스트(Linked List) |
| 크기 | 고정적 (동적 배열은 재할당으로 크기 조절) | 동적 (필요할 때마다 노드 추가/제거) |
| 메모리 할당 | 연속적인 메모리 공간에 할당 | 힙(Heap) 영역의 불연속적인 빈 공간에 노드 할당 |
| 데이터 참조 | O(1) (인덱스를 통한 직접 접근) | O(N) (순차적으로 포인터 따라 이동) |
| 삽입 / 삭제 | O(N) (데이터 이동 필요) | O(N) (삽입/삭제 위치 탐색 시간 포함) O(1) (위치를 알고 있을 때 작업 자체) |
| 메모리 오버헤드 | 적음 (순수 데이터만 저장) | 많음 (데이터 + 포인터 필드) |
| 캐시 효율성 | 좋음 (메모리 지역성) | 낮음 (불연속적인 할당) |
언제 무엇을 사용해야 될까?
배열 (또는 동적 배열)
- 데이터의 수가 자주 바뀌지 않고, 특정 인덱스에 대한 참조(읽기/쓰기)가 빈번하게 일어나는 경우에 효율적이다
- 데이터 접근 속도가 최우선일 때 유리하다
연결 리스트
- 데이터의 삽입과 삭제가 빈번하게 일어나 데이터의 크기가 자주 바뀌는 경우에 효율적이다
- 메모리를 유연하게 사용하고 싶을 때 적합하다
데이터의 양이 적을 경우 두 자료구조 간의 성능 차이가 미미할 수 있지만, 데이터의 양이 많아질수록 위에서 언급한 시간 복잡도와 메모리 효율성 차이가 전체적인 프로그램의 성능에 훨씬 더 큰 영향을 미칠 것이다
노드(Node) 클래스
public class Node {
Object data;
Node next; // 다음 노드를 가리키는 참조 변수, 기본값은 null
// 생성자: 데이터와 다음 노드를 인자도 받음
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
// 생성자 오버로드: 다음 노드를 지정하지 않을 경우 null로 초기화
public Node(Object data) {
this.data = data;
this.next = null; // next의 기본값을 null로 설정
}
// data를 가져오는 getter 메서드
public Object getData() {
return data;
}
// 다음 노드를 가져오는 getter 메서드
public Node getNext() {
return next;
}
// data를 설정하는 setter 메서드
public void setData(int data) {
this.data = data;
}
// 다음 노드를 설정하는 setter 메서드
public void setNext(Node next) {
this.next = next;
}
}
연결 리스트 (LinkedList) 클래스
public class LinkedList {
private Node head; // 연결 리스트의 시작 노드 (외부에서 직접 접근 방지)
private int size; // 저장된 노드의 총 개수
// 생성자
public LinkedList() {
this.head = null; // 초기에는 빈 리스트이므로 head는 null
this.size = 0; // 초기 노드 크기는 0
}
/**
* 리스트의 모든 요소를 출력한다
*/
public void printAll() {
Node currentNode = this.head;
StringBuilder sb = new StringBuilder("[");
while (currentNode != null) {
sb.append(currentNode.data); // 현재 노드의 데이터 추가
currentNode = currentNode.next; // 다음 노드로 이동
if (currentNode != null) {
sb.append(", "); // 다음 노드가 있으면 콤마와 공백 추가
}
}
sb.append("]");
System.out.println(sb.toString());
}
/**
* 리스트의 모든 원소를 제거한다
*/
public void clear() {
this.head = null; // head를 null로 설정하여 모든 참조 끊기
this.size = 0; // 노드 개수 0으로 초기화
System.out.println("리스트가 비워졌습니다.");
}
/**
* 지정된 인덱스에 데이터를 삽입한다
* @param index 삽입할 위치
* @param data 삽입할 데이터
* @throws IndexOutOfBoundsException 인덱스가 유효 범위를 벗어나는 경우
*/
public void insertAt(int index, int data) {
// 유효하지 않은 인덱스에 대한 예뢰 처리
if (index > this.size || index < 0) {
String message = "인덱스 " + index + "는 유효한 범위 [" + 0 + ", " + this.size + "]를 벗어났습니다.";
throw new IndexOutOfBoundsException(message);
}
Node newNode = new Node(data); // 새 노드 생성
// 1. 가장 앞부분에 삽입하는 경우 (index == 0)
if (index == 0) {
newNode.next = this.head; // 새 노드의 next가 현재 head를 가리키게 함
this.head = newNode; // head를 새 노드로 변경
} else {
// 2. 중간 또는 마지막에 삽입하는 경우
Node currentNode = this.head;
// 삽입할 위치의 바로 이전 노드까지 이동
for (int i = 0; i < index - 1; i++) {
currentNode = currentNode.next;
}
newNode.next = currentNode.next; // 새 노드의 next가 현재 노드의 next를 가리키게 함
currentNode.next = newNode; // 현재 노드의 next가 새 노드를 가리키게 함
}
this.size++; // 노드 개수 증가
System.out.println(data + "를 인덱스 " + index + "에 삽입했습니다.");
}
/**
* 리스트의 마지막에 데이터를 삽입한다
* @param data 삽입할 데이터
*/
public void insertLast(int data) {
insertAt(this.size, data); // 현재 size 위치에 삽입하면 마지막에 추가됨
System.out.println(data + "를 리스트의 마지막에 삽입했습니다.");
}
/**
* 지정된 인덱스의 노드를 제거하고 반환한다.
* @param index 제거할 노드의 인덱스
* @return 제거된 노드
* @throws IndexOutOfBoundsException 인덱스가 유효 범위를 벗어나는 경우
*/
public Node deleteAt(int index) {
// 유효하지 않은 인덱스에 대한 예외 처리
if (index >= this.size || index < 0) {
String message = "인덱스 " + index + "는 유효한 범위 [" + 0 + ", " + (this.size - 1) + "]를 벗어났습니다. 제거할 수 없습니다.";
throw new IndexOutOfBoundsException(message);
}
Node deletedNode; // 제거될 노드를 저장할 변수
// 1. head 노드를 제거하는 경우 (index == 0)
if (index == 0) {
deletedNode = this.head; // 제거될 노드를 현재 head로 설정
this.head = this.head.next; // head를 다음 노드로 변경
} else {
// 2. head 외의 노드를 제거하는 경우
Node currentNode = this.head;
// 제거할 노드의 바로 이전 노드까지 이동
for (int i = 0; i < index - 1; i++) {
currentNode = currentNode.next;
}
deletedNode = currentNode.next; // 제거될 노드를 현재 노드의 next로 설정
currentNode.next = deletedNode.next; // 현재 노드의 next가 제거될 노드의 next를 가리키게 함
}
this.size--; // 노드 개수 감소
System.out.println("인덱스 " + index + "의 노드 (" + deletedNode.data + ")를 제거했습니다.");
return deletedNode; // 제거된 노드를 반환
}
/**
* 리스트의 마지막 노드를 제거한다
* @return 제거된 마지막 노드
* @throws IndexOutOfBoundsException 리스트가 비어있을 경우
*/
public Node deleteLast() {
if (this.size == 0) {
throw new IndexOutOfBoundsException("리스트가 비어있어 제거할 노드가 없습니다.");
}
Node deletedNode = deleteAt(this.size -1); // 마지막 노드는 size - 1 인덱스에 해당
System.out.println("리스트의 마지막 노드 (" + deletedNode.data + ")를 제거했습니다.");
return deletedNode;
}
/**
* 지정된 인덱스의 노드를 반환한다
* @param index 가져올 노드의 인덱스
* @return 해당 인덱스의 노드
* @throws IndexOutOfBoundsException 인덱스가 유효 범위를 벗어나는 경우
*/
public Node getNodeAt(int index) {
// 유효하지 않은 인덱스에 대한 예외 처리
if (index >= this.size || index < 0) {
String message = "인덱스 " + index + "는 유효한 범위 [" + 0 + ", " + (this.size - 1) + "]를 벗어났습니다.";
throw new IndexOutOfBoundsException(message);
}
Node currentNode = this.head;
// 해당 인덱스까지 노드 순회
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
System.out.println("인덱스 " + index + "의 노드: " + currentNode.data);
return currentNode; // 해당 인덱스의 노드 반환
}
/**
* 리스트의 현재 크기를 반환한다
* @return 리스트의 노드 개수
*/
public int getSize() {
return this.size;
}
}
테스트 코드 및 실행
노드 클래스 테스트
package linkedList;
public class Main {
public static void main(String[] args) {
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
node1.setNext(node2); // node1.next = node2;
node2.setNext(node3); // node2.next = node3;
System.out.println("node1.data: " + node1.getData());
System.out.println("node1.next.data: " + node1.getNext().getData());
System.out.println("node1.next.next.data: " + node1.getNext().getNext().getData());
}
}
// node1.data: 1
// node1.next.data: 2
// node1.next.next.data: 3
LinkedList 클래스 – printAll(), clear()
package linkedList;
public class LinkedList {
private Node head; // 연결 리스트의 시작 노드 (외부에서 직접 접근 방지)
private int size; // 저장된 노드의 총 개수
public LinkedList() {
this.head = null; // 초기에는 빈 리스트이므로 head는 null
this.size = 0; // 초기 노드 크기는 0
}
public void printAll() {
Node currentNode = this.head;
StringBuilder sb = new StringBuilder("[");
while (currentNode != null) {
sb.append(currentNode.data); // 현재 노드의 데이터 추가
currentNode = currentNode.next; // 다음 노드로 이동
if (currentNode != null) {
sb.append(", "); // 다음 노드가 있으면 콤마와 공백 추가
}
}
sb.append("]");
System.out.println(sb.toString());
}
public void clear() {
this.head = null; // head를 null로 설정하여 모든 참조 끊기
this.size = 0; // 노드 개수 0으로 초기화
System.out.println("리스트가 비워졌습니다.");
}
}
LinkedList 클래스 테스트 – insertAt()
public class LinkedList {
private Node head; // 연결 리스트의 시작 노드 (외부에서 직접 접근 방지)
private int size; // 저장된 노드의 총 개수
/**
* 지정된 인덱스에 데이터를 삽입한다
* @param index 삽입할 위치
* @param data 삽입할 데이터
* @throws IndexOutOfBoundsException 인덱스가 유효 범위를 벗어나는 경우
*/
public void insertAt(int index, int data) {
// 유효하지 않은 인덱스에 대한 예외 처리
if (index > this.size || index < 0) {
String message = "인덱스 " + index + "는 유효한 범위 [" + 0 + ", " + this.size + "]를 벗어났습니다.";
throw new IndexOutOfBoundsException(message);
}
Node newNode = new Node(data); // 새 노드 생성
// 1. 가장 앞부분에 삽입하는 경우 (index == 0)
if (index == 0) {
newNode.next = this.head; // 새 노드의 next가 현재 head를 가리키게 함
this.head = newNode; // head를 새 노드로 변경
} else {
// 2. 중간 또는 마지막에 삽입하는 경우
Node currentNode = this.head;
// 삽입할 위치의 바로 이전 노드까지 이동
for (int i = 0; i < index - 1; i++) {
currentNode = currentNode.next;
}
newNode.next = currentNode.next; // 새 노드의 next가 현재 노드의 next를 가리키게 함
currentNode.next = newNode; // 현재 노드의 next가 새 노드를 가리키게 함
}
this.size++; // 노드 개수 증가
System.out.println(data + "를 인덱스 " + index + "에 삽입했습니다.");
}
}
package linkedList;
public class Main {
public static void main(String[] args) {
LinkedList list = new LinkedList();
try {
list.insertAt(0, 0);
list.insertAt(1, 1);
list.insertAt(2, 2);
list.insertAt(3, 3);
list.insertAt(4, 4);
list.printAll(); // [0, 1, 2, 3, 4]
} catch (IndexOutOfBoundsException e) {
System.out.println("에러 발생: " + e.getMessage());
}
}
}
// 0를 인덱스 0에 삽입했습니다.
// 1를 인덱스 1에 삽입했습니다.
// 2를 인덱스 2에 삽입했습니다.
// 3를 인덱스 3에 삽입했습니다.
// 4를 인덱스 4에 삽입했습니다.
// [0, 1, 2, 3, 4]
LinkedList 클래스 테스트 – clear()
package linkedList;
public class Main {
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.clear();
list.printAll(); // []
}
}
// 리스트가 비워졌습니다.
// []
LinkedList 클래스 테스트 – insertLast()
package linkedList;
public class LinkedList {
private Node head; // 연결 리스트의 시작 노드 (외부에서 직접 접근 방지)
private int size; // 저장된 노드의 총 개수
// 생성자
public LinkedList() {
this.head = null; // 초기에는 빈 리스트이므로 head는 null
this.size = 0; // 초기 노드 크기는 0
}
/**
* 리스트의 마지막에 데이터를 삽입한다
* @param data 삽입할 데이터
*/
public void insertLast(int data) {
insertAt(this.size, data); // 현재 size 위치에 삽입하면 마지막에 추가됨
System.out.println(data + "를 리스트의 마지막에 삽입했습니다.");
}
}
package linkedList;
public class Main {
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.insertLast(0);
list.insertLast(1);
list.insertLast(2);
list.printAll(); // [0, 1, 2]
}
}
// 0를 인덱스 0에 삽입했습니다.
// 0를 리스트의 마지막에 삽입했습니다.
// 1를 인덱스 1에 삽입했습니다.
// 1를 리스트의 마지막에 삽입했습니다.
// 2를 인덱스 2에 삽입했습니다.
// 2를 리스트의 마지막에 삽입했습니다.
// [0, 1, 2]
LinkedList 클래스 테스트 – deleteAt()
package linkedList;
public class LinkedList {
private Node head; // 연결 리스트의 시작 노드 (외부에서 직접 접근 방지)
private int size; // 저장된 노드의 총 개수
/**
* 지정된 인덱스의 노드를 제거하고 반환한다.
* @param index 제거할 노드의 인덱스
* @return 제거된 노드
* @throws IndexOutOfBoundsException 인덱스가 유효 범위를 벗어나는 경우
*/
public Node deleteAt(int index) {
// 유효하지 않은 인덱스에 대한 예외 처리
if (index >= this.size || index < 0) {
String message = "인덱스 " + index + "는 유효한 범위 [" + 0 + ", " + (this.size - 1) + "]를 벗어났습니다. 제거할 수 없습니다.";
throw new IndexOutOfBoundsException(message);
}
Node deletedNode; // 제거될 노드를 저장할 변수
// 1. head 노드를 제거하는 경우 (index == 0)
if (index == 0) {
deletedNode = this.head; // 제거될 노드를 현재 head로 설정
this.head = this.head.next; // head를 다음 노드로 변경
} else {
// 2. head 외의 노드를 제거하는 경우
Node currentNode = this.head;
// 제거할 노드의 바로 이전 노드까지 이동
for (int i = 0; i < index - 1; i++) {
currentNode = currentNode.next;
}
deletedNode = currentNode.next; // 제거될 노드를 현재 노드의 next로 설정
currentNode.next = deletedNode.next; // 현재 노드의 next가 제거될 노드의 next를 가리키게 함
}
this.size--; // 노드 개수 감소
System.out.println("인덱스 " + index + "의 노드 (" + deletedNode.data + ")를 제거했습니다.");
return deletedNode; // 제거된 노드를 반환
}
}
package linkedList;
public class LinkedList {
private Node head; // 연결 리스트의 시작 노드 (외부에서 직접 접근 방지)
private int size; // 저장된 노드의 총 개수
/**
* 지정된 인덱스의 노드를 제거하고 반환한다.
* @param index 제거할 노드의 인덱스
* @return 제거된 노드
* @throws IndexOutOfBoundsException 인덱스가 유효 범위를 벗어나는 경우
*/
public Node deleteAt(int index) {
// 유효하지 않은 인덱스에 대한 예외 처리
if (index >= this.size || index < 0) {
String message = "인덱스 " + index + "는 유효한 범위 [" + 0 + ", " + (this.size - 1) + "]를 벗어났습니다. 제거할 수 없습니다.";
throw new IndexOutOfBoundsException(message);
}
Node deletedNode; // 제거될 노드를 저장할 변수
// 1. head 노드를 제거하는 경우 (index == 0)
if (index == 0) {
deletedNode = this.head; // 제거될 노드를 현재 head로 설정
this.head = this.head.next; // head를 다음 노드로 변경
} else {
// 2. head 외의 노드를 제거하는 경우
Node currentNode = this.head;
// 제거할 노드의 바로 이전 노드까지 이동
for (int i = 0; i < index - 1; i++) {
currentNode = currentNode.next;
}
deletedNode = currentNode.next; // 제거될 노드를 현재 노드의 next로 설정
currentNode.next = deletedNode.next; // 현재 노드의 next가 제거될 노드의 next를 가리키게 함
}
this.size--; // 노드 개수 감소
System.out.println("인덱스 " + index + "의 노드 (" + deletedNode.data + ")를 제거했습니다.");
return deletedNode; // 제거된 노드를 반환
}
}
package linkedList;
public class Main {
public static void main(String[] args) {
LinkedList list = new LinkedList();
try {
list.deleteAt(0); // 0번 인덱스 (데이터 0) 삭제
list.printAll(); // [1, 2]
list.deleteAt(1); // 1번 인덱스 (데이터 2) 삭제 (인덱스가 밀려서 변경됨)
list.printAll(); // [1]
} catch (IndexOutOfBoundsException e) {
System.out.println("에러 발생: " + e.getMessage());
}
}
}
// 인덱스 0의 노드 (0)를 제거했습니다.
// [1, 2]
// 인덱스 1의 노드 (2)를 제거했습니다.
// [1]
LinkedList 클래스 테스트 – deleteLast()
package linkedList;
public class LinkedList {
private Node head; // 연결 리스트의 시작 노드 (외부에서 직접 접근 방지)
private int size; // 저장된 노드의 총 개수
/**
* 리스트의 마지막 노드를 제거한다
* @return 제거된 마지막 노드
* @throws IndexOutOfBoundsException 리스트가 비어있을 경우
*/
public Node deleteLast() {
if (this.size == 0) {
throw new IndexOutOfBoundsException("리스트가 비어있어 제거할 노드가 없습니다.");
}
Node deletedNode = deleteAt(this.size -1); // 마지막 노드는 size - 1 인덱스에 해당
System.out.println("리스트의 마지막 노드 (" + deletedNode.data + ")를 제거했습니다.");
return deletedNode;
}
}
package linkedList;
public class Main {
public static void main(String[] args) {
LinkedList list = new LinkedList();
try {
list.insertLast(5); // [1, 5]
list.printAll();
list.deleteLast(); // 5 삭제
list.printAll(); // [1]
list.deleteLast(); // 1 삭제
list.printAll(); // []
// list.deleteLast(); // 이 경우 에러 발생 (리스트가 비어있음)
} catch (IndexOutOfBoundsException e) {
System.out.println("에러 발생: " + e.getMessage());
}
}
}
// 5를 인덱스 1에 삽입했습니다.
// 5를 리스트의 마지막에 삽입했습니다.
// [1, 5]
// 인덱스 1의 노드 (5)를 제거했습니다.
// 리스트의 마지막 노드 (5)를 제거했습니다.
// [1]
// 인덱스 0의 노드 (1)를 제거했습니다.
// 리스트의 마지막 노드 (1)를 제거했습니다.
// []
LinkedList 클래스 테스트 – getNodeAt()
package linkedList;
public class Main {
public static void main(String[] args) {
LinkedList list = new LinkedList();
try {
list.insertLast(1); // [1]
list.insertLast(2); // [1, 2]
list.insertLast(3); // [1, 2, 3]
list.insertLast(4); // [1, 2, 3, 4]
list.printAll();
Node secondNode = list.getNodeAt(2); // 인덱스 2는 3번째 데이터 (3)
System.out.println("두 번째 인덱스 노드의 데이터: " + secondNode.getData()); // 3
System.out.println("Node 객체 자체: " + secondNode); // Node{data=3} (toString 오버라이드 시)
} catch (IndexOutOfBoundsException e) {
System.out.println("에러 발생: " + e.getMessage());
}
}
}
// 1를 인덱스 0에 삽입했습니다.
// 1를 리스트의 마지막에 삽입했습니다.
// 2를 인덱스 1에 삽입했습니다.
// 2를 리스트의 마지막에 삽입했습니다.
// 3를 인덱스 2에 삽입했습니다.
// 3를 리스트의 마지막에 삽입했습니다.
// 4를 인덱스 3에 삽입했습니다.
// 4를 리스트의 마지막에 삽입했습니다.
// [1, 2, 3, 4]
// 인덱스 2의 노드: 3
// 두 번째 인덱스 노드의 데이터: 3
// Node 객체 자체: linkedList.Node@133314b
LinkedList 클래스 테스트 – 예외 처리
package linkedList;
public class Main {
public static void main(String[] args) {
LinkedList emptyList = new LinkedList();
try {
emptyList.deleteAt(0); // 비어있는 리스트에서 삭제 시도
} catch (IndexOutOfBoundsException e) {
System.out.println("예상된 에러: " + e.getMessage());
}
try {
emptyList.getNodeAt(0); // 비어있는 리스트에서 노드 가져오기 시도
} catch (IndexOutOfBoundsException e) {
System.out.println("예상된 에러: " + e.getMessage());
}
try {
emptyList.insertAt(1, 100); // 범위를 벗어난 인덱스에 삽입 시도
} catch (IndexOutOfBoundsException e) {
System.out.println("예상된 에러: " + e.getMessage());
}
}
}
// 예상된 에러: 인덱스 0는 유효한 범위 [0, -1]를 벗어났습니다. 제거할 수 없습니다.
// 예상된 에러: 인덱스 0는 유효한 범위 [0, -1]를 벗어났습니다.
// 예상된 에러: 인덱스 1는 유효한 범위 [0, 0]를 벗어났습니다.