본문 바로가기

Coding Language/Java

[Java] Java로 구현하는 Doubly Linked List

[해당 포스트는 개인적으로 공부를 하고 차후에 참고용으로 하고자 작성한 것입니다.

따라서 잘못된 부분이나 부족한 부분이 있을 수 있기에 참고하시기 바랍니다.]

 

저번 포스팅으로 Linked List에 대한 실습을 해보았었다.

이번 시간엔 Linked List를 직접 구현해보고 테스트해보는 실습을 하였다. 그러나 추가적인 지시사항이 있었다 하면 Doubly Linked List로 구현하고, Integer 한정으로 처리할 수 있는 Linked List를 구현하며, CRUD를 만족하라고 하셨기에 조건에 맞춰 구현을 했다.

 

이번 실습에선 addLast와 removeLast 구현은 생략했다.
이건 각자 소스코드를 참조하여 구현하면 될 듯하다.

 

자세한 설명은 주석으로 다 적어놨으니 참고하여 읽으면 될 듯하다.

 

Node Class

Node Class는 MyLinkedList의 Inner Class로 넣었으므로 private Type로 설정.

맨 밑에 Full Source Code를 참조하자.

 

/** 
 * 새로운 노드를 추가할 수 있도록
 * 내부 Class로 Node를 만든다.
 * */
private class Node{
	private Node nextNode;
	private Node prevNode;
	private int data;
	
	/** 
	 * Node 생성자 호출 작업
	 * 해당 Node객체를 초기화 시킨다.
	 * @param data 새로 추가할 데이터
	 * */
	public Node(int data) {
		this.data = data;
		this.nextNode = null;
		this.prevNode = null;
	}
}

CRUD (Create)

/** 
 * Doubly Linked List 맨 앞에 새로운 Node를 추가하는 Method.
 * 1. 새로운 Node를 추가하면 Node객체를 생성한다.
 * 2. 이미 head가 Node를 가르키고 있다면 앞 자리에 Node를 삽입한다.
 * 3. head를 새로 들어온 Node의 인스턴스 정보를 저장한다.
 * 4. 만약 새로운 Node의 Next가 null일 경우 tail에 head를 추가한다.
 * 5. Doubly Linked List크기를 증가시킨다.
 * @param data 새로 추가할 데이터
 * */
public void addFirst(int data) {
	Node newNode = new Node(data);
	
	/*
	 * 이미 head에 데이터가 있는 경우.
	 * 1. head가 null을 가리키고 있지 않다.
	 * 2. 다음 새로 추가되는 Node의 Next를 기존에 있던 Node의
	 *    인스턴스 정보를 새로운 Node의 Next로 가르킨다.
	 * 3. head는 가장 앞 자리 Node의 인스턴스 정보를 가진다.
	 * 4. 기존에 있던 Node의 Prev를 새로운 노드로 설정한다.
	 * */
	if (head != null) {
		newNode.nextNode = head;
		head.prevNode = newNode;
	}
	
	/*
	 * head의 Next는 새로 들어오는 Node의 인스턴스정보를 가진다.
	 * */
	head = newNode;
	
	/*
	 * 만약 새로운 Node의 Next를 가르키는 정보가 없다면,
	 * head에 tail을 넣어준다.
	 * */
	if (head.nextNode == null) {
		tail = head;
	}
	size++;
}
/** 
 * 원하는 Index에 data를 추가시켜주는 Method.
 * @param index Linked List에 들어갈 Index 번호
 * @param data Node에 추가할 Data
 * */
public void add(int index, int data) {
	/*
	 * 만약 Index가 0이면, 비어있다는 뜻.
	 * 따라서 제일 처음자리에 Node를 생성하여 Data를 넣는다.
	 * 만약 Index가 0이 아니면, 이미 Node가 있다는 뜻.
	 * 새로운 Node를 생성하여 Data를 넣는다.
	 * 새로 추가된 Node의 Prev를 가리키기 위해 
	 * index - 1번째의 Node Instance 정보를 가져온다.
	 * 새로운 Node의 Next를 가리키기 위해
	 * 이전 Node에 있던 Next를 가져온다.
	 * */
	if (index == 0) {
		addFirst(data);
	}else {
		Node newNode = new Node(data);
		Node prevNode = getNode(index - 1);
		Node nextNode = prevNode.nextNode;
		
		newNode.prevNode = prevNode;
		newNode.nextNode = nextNode;
		
		prevNode.nextNode = newNode;
		nextNode.prevNode = newNode;
		
		size++;
	}
}

CRUD (Read)

/** 
 * Linked List에 저장된 Data의 항목을 출력하는 Method.
 * 만약 size가 0이면 Linked List가 비어있으므로, 바로 return 한다.
 * 만약 size가 0이 아니라면, Node를 순차적으로 탐색하며
 * StringBuffer에 넣어준다.
 * @return result 순환하며 저장된 Node list를 반환한다. 
 * */
public String toString() {
	StringBuffer result = new StringBuffer("[");
	if (size != 0) {
		Node node = head;
		result.append(node.data);
		
		while (node.nextNode != null) {
			node = node.nextNode;
			result.append(", ");
			result.append(node.data);
		}
	}
	result.append("]");
	
	return result.toString();
}

CRUD (Update)

/** 
 * Index에 위치한 Node의 Data를 변경하는 Method.
 * 만약 크기가 0이면 Linked List가 비어있으므로, 바로 return 한다.
 * 만약 크기가 0이 아니면 Linked List에 Node가 존재하므로,
 * 해당 Node의 인스턴스를 가져와 data를 교체한다.
 * @param index Index에 속하는 Node
 * @param data 변경하고자 하는 Data
 * @return boolean 정상적으로 변경됬는지에 대한 Boolean.
 * */
public boolean update(int index, int data) {
	if (size == 0)
		return false;
	
	Node node = getNode(index);
	node.data = data;
	
	return true;
}

CRUD (Delete)

/** 
 * 제일 첫 번째 Node를 삭제하는 Method.
 * 만약 크기가 비어있다면 Linked List가 비어있으므로, 바로 return 한다.
 * 만약 크기가 0이 아니면 Linked List에 Node가 있으므로,
 * head가 가리키던 Node 인스턴스를 가져온다.
 * head는 삭제되던 Node가 Next하던 Tail로 가리키고
 * 삭제되는 Node의 prev를 null로 설정하여 연결을 끊는다.
 * @return boolean 정상적으로 삭제됬는지에 대한 Boolean.
 * */
public boolean removeFirst() {
	if (size == 0)
		return false;
	
	Node removeNode = head;
	head = removeNode.nextNode;
	head.prevNode = null;
	
	size--;
	
	return true;
}
/** 
 * 해당하는 Index의 Node를 제거하는 Method.
 * 만약 크기가 0이면, Linked List에 제거할 Node가 없기에 바로 return.
 * 만약 index가 0이면, 제일 첫 번째 Node를 제거하는 removeFirst() 호출.
 * 만약 index가 0이 아니면, 제거하는 Node의 인스턴스를 가져온다.
 * prev와 next Node 정보를 가져온 후,
 * 이전 Node는 삭제하는 Node의 next를 update시키고,
 * 삭제하는 Node의 다음 Node의 prev를 update시킨다.
 * @return boolean 정상적으로 삭제됬는지에 대한 Boolean.
 * */
public boolean remove(int index) {
	if (size == 0)
		return false;
	
	if (index == 0) {
		return removeFirst();
	} else {
		Node removeNode = getNode(index);
		Node prevNode = removeNode.prevNode;
		Node nextNode = removeNode.nextNode;
		
		prevNode.nextNode = nextNode;
		nextNode.prevNode = prevNode;
		
		size--;
		
		return true;
	}
}

Full Source Code

public class MyLinkedList {
	private Node head;
	private Node tail;
	private int size;
	
	/**
	 * 새로운 Linked List를 생성하면,
	 *  크기 0으로 초기화 시칸다.
	 *  */
	public MyLinkedList() {
		size = 0;
	}
	
	/** 
	 * 새로운 노드를 추가할 수 있도록
	 * 내부 Class로 Node를 만든다.
	 * */
	private class Node{
		private Node nextNode;
		private Node prevNode;
		private int data;
		
		/** 
		 * Node 생성자 호출 작업
		 * 해당 Node객체를 초기화 시킨다.
		 * @param data 새로 추가할 데이터
		 * */
		public Node(int data) {
			this.data = data;
			this.nextNode = null;
			this.prevNode = null;
		}
	}
	
	/** 
	 * Doubly Linked List 맨 앞에 새로운 Node를 추가하는 Method.
	 * 1. 새로운 Node를 추가하면 Node객체를 생성한다.
	 * 2. 이미 head가 Node를 가르키고 있다면 앞 자리에 Node를 삽입한다.
	 * 3. head를 새로 들어온 Node의 인스턴스 정보를 저장한다.
	 * 4. 만약 새로운 Node의 Next가 null일 경우 tail에 head를 추가한다.
	 * 5. Doubly Linked List크기를 증가시킨다.
	 * @param data 새로 추가할 데이터
	 * */
	public void addFirst(int data) {
		Node newNode = new Node(data);
		
		/*
		 * 이미 head에 데이터가 있는 경우.
		 * 1. head가 null을 가리키고 있지 않다.
		 * 2. 다음 새로 추가되는 Node의 Next를 기존에 있던 Node의
		 *    인스턴스 정보를 새로운 Node의 Next로 가르킨다.
		 * 3. head는 가장 앞 자리 Node의 인스턴스 정보를 가진다.
		 * 4. 기존에 있던 Node의 Prev를 새로운 노드로 설정한다.
		 * */
		if (head != null) {
			newNode.nextNode = head;
			head.prevNode = newNode;
		}
		
		/*
		 * head의 Next는 새로 들어오는 Node의 인스턴스정보를 가진다.
		 * */
		head = newNode;
		
		/*
		 * 만약 새로운 Node의 Next를 가르키는 정보가 없다면,
		 * head에 tail을 넣어준다.
		 * */
		if (head.nextNode == null) {
			tail = head;
		}
		size++;
	}
	
	/** 
	 * 원하는 Index에 data를 추가시켜주는 Method.
	 * @param index Linked List에 들어갈 Index 번호
	 * @param data Node에 추가할 Data
	 * */
	public void add(int index, int data) {
		/*
		 * 만약 Index가 0이면, 비어있다는 뜻.
		 * 따라서 제일 처음자리에 Node를 생성하여 Data를 넣는다.
		 * 만약 Index가 0이 아니면, 이미 Node가 있다는 뜻.
		 * 새로운 Node를 생성하여 Data를 넣는다.
		 * 새로 추가된 Node의 Prev를 가리키기 위해 
		 * index - 1번째의 Node Instance 정보를 가져온다.
		 * 새로운 Node의 Next를 가리키기 위해
		 * 이전 Node에 있던 Next를 가져온다.
		 * */
		if (index == 0) {
			addFirst(data);
		}else {
			Node newNode = new Node(data);
			Node prevNode = getNode(index - 1);
			Node nextNode = prevNode.nextNode;
			
			newNode.prevNode = prevNode;
			newNode.nextNode = nextNode;
			
			prevNode.nextNode = newNode;
			nextNode.prevNode = newNode;
			
			size++;
		}
	}
	
	/** 
	 * 해당하는 Index의 Node 인스턴스를 가져오는 Method.
	 * @param index Linked List에 속해있는 Node의 index.
	 * @return node Index에 속하는 Node.
	 * */
	private Node getNode(int index) {
		/*
		 * 만약 index의 크기가 전체 크기의 절반보다 작으면
		 * head에서 부터 검색을 시작한다.
		 * index 위치까지 반복하여 해당 Node의 정보를 node에 저장한다.
		 * 만약 index의 크기가 전체 크기의 절반보다 크면
		 * tail에서 부터 검색을 시작한다.
		 * */
		if (index < size / 2) {
			Node node = head;
			for (int i = 0; i < index; i++) {
				node = node.nextNode;
			}
			
			return node;
		}else {
			Node node = tail;
			for (int i = size - 1; i > index; i--) {
				node = node.prevNode;
			}
			
			return node;
		}
	}
	
	/** 
	 * 제일 첫 번째 Node를 삭제하는 Method.
	 * 만약 크기가 비어있다면 Linked List가 비어있으므로, 바로 return 한다.
	 * 만약 크기가 0이 아니면 Linked List에 Node가 있으므로,
	 * head가 가리키던 Node 인스턴스를 가져온다.
	 * head는 삭제되던 Node가 Next하던 Tail로 가리키고
	 * 삭제되는 Node의 prev를 null로 설정하여 연결을 끊는다.
	 * @return boolean 정상적으로 삭제됬는지에 대한 Boolean.
	 * */
	public boolean removeFirst() {
		if (size == 0)
			return false;
		
		Node removeNode = head;
		head = removeNode.nextNode;
		head.prevNode = null;
		
		size--;
		
		return true;
	}
	
	/** 
	 * 해당하는 Index의 Node를 제거하는 Method.
	 * 만약 크기가 0이면, Linked List에 제거할 Node가 없기에 바로 return.
	 * 만약 index가 0이면, 제일 첫 번째 Node를 제거하는 removeFirst() 호출.
	 * 만약 index가 0이 아니면, 제거하는 Node의 인스턴스를 가져온다.
	 * prev와 next Node 정보를 가져온 후,
	 * 이전 Node는 삭제하는 Node의 next를 update시키고,
	 * 삭제하는 Node의 다음 Node의 prev를 update시킨다.
	 * @return boolean 정상적으로 삭제됬는지에 대한 Boolean.
	 * */
	public boolean remove(int index) {
		if (size == 0)
			return false;
		
		if (index == 0) {
			return removeFirst();
		} else {
			Node removeNode = getNode(index);
			Node prevNode = removeNode.prevNode;
			Node nextNode = removeNode.nextNode;
			
			prevNode.nextNode = nextNode;
			nextNode.prevNode = prevNode;
			
			size--;
			
			return true;
		}
	}
	
	/** 
	 * Index에 위치한 Node의 Data를 변경하는 Method.
	 * 만약 크기가 0이면 Linked List가 비어있으므로, 바로 return 한다.
	 * 만약 크기가 0이 아니면 Linked List에 Node가 존재하므로,
	 * 해당 Node의 인스턴스를 가져와 data를 교체한다.
	 * @param index Index에 속하는 Node
	 * @param data 변경하고자 하는 Data
	 * @return boolean 정상적으로 변경됬는지에 대한 Boolean.
	 * */
	public boolean update(int index, int data) {
		if (size == 0)
			return false;
		
		Node node = getNode(index);
		node.data = data;
		
		return true;
	}
	
	/** 
	 * Linked List에 저장된 Data의 항목을 출력하는 Method.
	 * 만약 size가 0이면 Linked List가 비어있으므로, 바로 return 한다.
	 * 만약 size가 0이 아니라면, Node를 순차적으로 탐색하며
	 * StringBuffer에 넣어준다.
	 * @return result 순환하며 저장된 Node list를 반환한다. 
	 * */
	public String toString() {
		StringBuffer result = new StringBuffer("[");
		if (size != 0) {
			Node node = head;
			result.append(node.data);
			
			while (node.nextNode != null) {
				node = node.nextNode;
				result.append(", ");
				result.append(node.data);
			}
		}
		result.append("]");
		
		return result.toString();
	}
	
	public static void main(String[] args) {
		MyLinkedList list = new MyLinkedList();
		
		list.addFirst(4);
		list.addFirst(3);
		list.addFirst(2);
		list.addFirst(1);
		System.out.println(list.toString());
		
		list.add(2, 25);
		System.out.println(list.toString());
		
		list.remove(1);
		System.out.println(list.toString());
		
		list.update(3, 13);
		System.out.println(list.toString());
	}
}