Java LinkedList - 직접 구현하기 1
노드의 연결 구조로 이어진 리스트를 직접 만들어보자.
이러한 자료 구조는 연결리스트(LinkedList) 이라 불리오고 있다.
리스트 자료 구조 특징
- 중복 허용
- 데이터 순서가 있음
연결 리스트는 배열 리스트의 단점인 메모리 공간과 성능을 개선할 수 있는 자료구조이다.
배열 리스트 단점
- 동적 배열 추가로 확보한 메모리 공간 낭비
- 리스트 앞 또는 중간에서 데이터 추가 시 데이터 이동의 성능 하락
배열 리스트, 연결 리스트는 모두 같은 리스트이며 노드의 연결 구조를 사용의 차이가 있다.
CLinkedList
자료구조 연결 리스트 직접 작성해보는 시간이다.
Node.class
public class Node {
Object item;
Node next;
public Node(Object item) {
this.item = item;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node n = this;
sb.append("[");
while (n != null) {
sb.append(n.item);
if (n.next != null) {
sb.append("->");
}
n = n.next;
}
sb.append("]");
return sb.toString();
}
}
- Node 멤버 필드와 생성자가 있다.
- toString()은 이전에 작성한 StringBuilder로 사용한다.
CLinkedList.class
public class CLinkedList {
Node first;
int size;
public void add(Object e) {
Node newNode = new Node(e);
if (first == null) {
// 노드를 처음으로 사용한 경우에만 수행함
first = newNode;
} else {
// 노드가 있는 경우 수행
Node lastNode = getLastNode();
lastNode.next = newNode;
}
size++;
}
private Node getLastNode() {
Node n = first;
while (n.next != null) {
n = n.next;
}
return n;
}
public Object set(int index, Object element) {
Node n = getNode(index);
Object oldValue = n.item;
n.item = element;
return oldValue;
}
public Object get(int index) {
Node node = getNode(index);
return node.item;
}
private Node getNode(int index) {
Node n = first;
for (int i = 0; i < index; i++) {
n = n.next;
}
return n;
}
public int indexOf(Object o) {
int index = 0;
for (Node n = first; n != null; n = n.next) {
if (o.equals(n.item)) {
return index;
}
index++;
}
return -1;
}
public int size() {
return size;
}
@Override
public String toString() {
return "CLinkedList{" +
"first=" + first +
", size=" + size +
'}';
}
}
- 자바 클래스로 CLinedList 새로 작성한다.
CLinkedList 멤버 필드
Node first;
int size;
- Node 타입의 first 변수를 사용한다.
- 노드에 입력한 데이터가 몇개 인지 확인하는 size 변수를 추가한다. 데이터가 추가될 때마다 하나씩 증가한다.
add()
public void add(Object e) {
Node newNode = new Node(e);
if (first == null) {
// 노드를 처음으로 사용한 경우에만 수행함
first = newNode;
} else {
// 노드가 있는 경우 수행
Node lastNode = getLastNode();
lastNode.next = newNode;
}
size++;
}
- 마지막 노드에 데이터를 추가한다.
- 노드가 없는 경우 비어있는 first 멤버 필드에 "새 노드"를 연결한다.
- 기존 노드가 있는 경우 "새 노드"를 마지막 노드에 연결한다.
set()
public Object set(int index, Object element) {
Node n = getNode(index);
Object oldValue = n.item;
n.item = element;
return oldValue;
}
- 인덱스 통해 특정 위치 데이터를 찾고 변경
- 기존에 있던 값을 반환한다.
get()
public Object get(int index) {
Node node = getNode(index);
return node.item;
}
- 인덱스 통해 특정 위치에 값을 반환한다.
- 조회 기능의 단점으로 getNode 루프를 돌아야한다.
- O(1) 성능
indexOf()
public int indexOf(Object o) {
int index = 0;
for (Node n = first; n != null; n = n.next) {
if (o.equals(n.item)) {
return index;
}
index++;
}
return -1;
}
- 객체 또는 값이 있는지 확인하는 메소드
getLastNode()
private Node getLastNode() {
Node n = first;
while (n.next != null) {
n = n.next;
}
return n;
}
- 마지막 노드 객체를 가져오기
getNode()
private Node getNode(int index) {
Node n = first;
for (int i = 0; i < index; i++) {
n = n.next;
}
return n;
}
- 인덱스 통해 객체 노드 가져오기
size()
public int size() {
return size;
}
- 노드의 사이즈
toString()
@Override
public String toString() {
return "CLinkedList{" +
"first=" + first +
", size=" + size +
'}';
}
- toString() 오버라이딩으로 출력 결과를 편하게 보기
메소드와 기능 사용해보기
작성한 CLinkedList 동작하는지 엔트리 포인트를 작성한다.
Main.class
CLinkedList list = new CLinkedList();
System.out.println("** 데이터 추가하기 **");
System.out.println(list);
list.add("a");
System.out.println(list);
list.add("b");
System.out.println(list);
list.add("c");
System.out.println(list);
list.add("c");
System.out.println(list);
System.out.println("** 메소드 기능 사용하기 **");
System.out.println("list.size() = " + list.size());
System.out.println("list.get(2) = " + list.get(2));
System.out.println("list.indexOf('c') = " + list.indexOf('c'));
System.out.println("list.set(2, 'z') = " + list.set(2, 'z'));
System.out.println(list);
** 데이터 추가하기 **
CLinkedList{first=null, size=0}
CLinkedList{first=[a], size=1}
CLinkedList{first=[a->b], size=2}
CLinkedList{first=[a->b->c], size=3}
CLinkedList{first=[a->b->c->c], size=4}
** 메소드 기능 사용하기 **
list.size() = 4
list.get(2) = c
list.indexOf('c') = -1
list.set(2, 'z') = c
CLinkedList{first=[a->b->z->c], size=4}
이전에 직접 구현한 CList 메인 호출에서 객체만 바꿔줘도 모두 정상적으로 동작한다.
연결 리스트와 빅오
get()
- 특정 위치 데이터를 반환한다.
- 데이터 증가에 따른 성능 추세 빅오 O(n)
- 배열 리스트는 원하는 데이터를 O(1) 성능으로 직접 꺼낼 수 있었다. 그렇지만 연결 리스트에 있는 노드들은 배열이 아니고 참조만 있을 뿐이다. 원하는 데이터를 찾으려면 인덱스 숫자 만큼이나 노드를 반복해서 찾는다.
- 연결리스트는 특정 위치의 노드 반환에 O(n) 시간이 소요된다.
add()
- 마지막 노드에 데이터 추가를 강제한다.
- 데이터 증가에 따른 성능 추세 빅오 O(n)
- 마지막 노드를 찾으므로 O(n) 이다.
- 찾아낸 마지막 노드에 새 노드 추가에는 O(1) 이다.
int indexOf()
- 데이터 검색과 검색된 위치를 반환한다.
- O(n)
- 모든 노드 순회하면서 equals() 메소드로 같은 데이터가 있는지 찾는다.
기존의 배열리스트의 단점을 해결했으나, 데이터 추가, 조회 자체에 O(1) 성능 하락이 있다.
정리
- 노드와 연결 개념이 숙지했다면 이 파트는 어렵지 않게 할 수 있을 것이다.
- 연결리스트의 데이터 추가는 필요한 메모리만 사용한다. 배열리스트에서 낭비되는 메모리를 해결할 수 있다.
- 연결 유지에는 추가 메모리가 이용되므로 단점도 존재한다.