1. Heap이란?

힙 (최대힙)

  • 힙은 우선순위 큐의 대표적인 자료구조이다.
  • 우선순위큐란 원소에 우선순위가 가장 큰 원소부터 접근이 가능한 자료구조이다.
  • 힙의 대표적인 종류로는 최대힙, 최소힙이 있으며 최대힙은 키값이 큰 원소가 높은 우선순위를, 최소힙은 키값이 작은 원소가 높은 우선순위를 가진다.
  • 최대힙 - 부모 노드의 원소가 자식 노드의 원소보다 크거나 같은 완전 이진트리
  • 최소힙 - 부모 노드의 원소가 자식 노드의 원소보다 작거나 같은 완전 이진트리
  • 힙에서 삽입 및 삭제의 시간 복잡도는 O(log2n)이다.

2. (최대)Heap 구현하기

구현할 내용은 아래와 같다.

  • heapifyUp(int i) - i번째 인덱스의 값을 부모 노드와 비교하여, 부모 노드의 값이 더 작으면 자리를 교환한다.
  • heapifyDwon(int i) - i번째 인덱스의 값을 자식 노드 두 개와 비교하여, 둘 중 큰 자식노드보다 i번째 인덱스 값이 더 작으면 자리를 교환한다.
  • insert(E x) - 힙의 적절한 자리에 입력받은 값 x를 입력한다.
  • delete() - 루트 노드에 있는 값을 삭제한다.
  • max() - 루트 노드에 있는 값을 리턴한다.
  • isEmpty() - 힙이 비어있으면 true, 그렇지 않으면 false를 리턴한다.
  • clear() - 힙의 모든 내용물을 비운다.
  • printAll() - 힙의 모든 내용물을 출력한다.

** 원래 힙에서는 맨 앞의 값에만 접근이 가능하나, 테스트 편의상 printAll() 메서드를 만들어 사용하였다.

 

1) 인터페이스

public interface heapInterface<E> {
	
	public void insert (E x);
	
	public void delete();
	
	public E max();
	
	public boolean isEmpty();
	
	public void clear();
	
	public void printAll();
	
}

 

2) 클래스

- 필드 영역

    //필드 영역
	private E[] item;
	private int size;
  • item[] - 원소를 저장한다.
  • size - 배열에 저장된 원소의 개수를 저장한다.

- 생성자 영역

	//생성자 영역
	public heap(int arraySize) {
		item = (E[]) new Comparable [arraySize];
		size=0;
	}
  • 배열의 크기를 입력받아 해당 크기의 배열을 생성한다. size변수는 0으로 초기화한다.

- 메서드 영역

<heapifyUp 메서드 구현>

	private void heapifyUp(int i) {
		
		if (i<0 || i>=item.length) {
			throw new IllegalArgumentException();
		} else {
		int parent = (i-1)/2;
		
		if (((Comparable) item[parent]).compareTo(item[i])<0) {
			E tmp = item[i];
			item[i] = item[parent];
			item[parent] = tmp;
			
		heapifyUp(parent);
			}
		}
	}
  • parent 변수에 입력받은 인덱스 노드의 부모 인덱스를 저장한다.
  • 만약 입력받은 인덱스 노드의 값보다 부모 노드의 값이 작으면 값을 바꾼다.
  • 부모 노드와 인덱스 노드의 값이 같거나, 부모 노드 값이 더 클 때까지 부모 노드에 대하여 해당 과정을 반복한다.
  • 만약 입력 받은 인덱스가 범위를 벗어났다면 에러를 리턴한다.

<heapifyDown 메서드 구현>

	private void heapifyDown(int i) {
		
		int left = 2*i+1;
		int right = 2*i+2;
		int max = i;
		if (i<0 || i>item.length) {
			throw new IllegalArgumentException();
		} else {
			if (left <=size-1) {
		if (((Comparable) item[max]).compareTo(item[left])<0) {
			max = left;
		} 
		if (((Comparable) item[max]).compareTo(item[right])<0) {
			max = right;
		}
		
		if (max!=i) {
			E tmp = item[i];
			item[i] = item[max];
			item[max] = tmp;
			
			heapifyDown(max); }
			}
		}
	}
  • left 변수에 입력받은 인덱스 노드의 왼쪽 자식 노드 인덱스, right 변수에 오른쪽 자식 노드 인덱스를 저장한다.
  • max 변수에 입력받은 인덱스를 저장한다.
  • left 변수가 범위를 벗어나지 않는 동안 아래 과정을 반복한다.
  • max 인덱스의 값과 left, right 인덱스 값을 비교하여 max 인덱스에 가장 큰 값의 인덱스가 들어오게 한다.
  • max의 값이 입력받은 값과 다르면(자식 노드중에 더 큰 값이 있다면) 입력 받은 인덱스 노드와 max 인덱스 노드의 자리를 바꾼다.
  • 입력받은 인덱스가 범위를 벗어났다면 에러를 리턴한다.

<insert 메서드 구현>

	@Override
	public void insert(E x) {
		if (size==item.length) {
			throw new IllegalArgumentException();
		} else {
		item[size]=x;
		heapifyUp(size);
		size++;
		}
	}
  • 힙의 끝에 입력받은 값을 추가한다.
  • heapifyUp을 통해 해당 노드가 적절한 자리를 찾게 한다.
  • size 변수를 1 증가시킨다. 
  • 만약 힙에 빈 자리가 없었다면 에러를 리턴한다.

<delete 메서드 구현>

	@Override
	public void delete() {
		if (isEmpty()) {
			throw new IllegalArgumentException();
		} else {
		item[0]=item[size-1];
		size--;
		heapifyDown(0);
		}
	}
  • 힙의 맨 끝자리에 있던 노드를 루트 노드 자리로 옮긴다.
  • size 변수를 1 감소시킨다.
  • heapifyDown을 통해 루트 노드 자리의 노드가 적절한 자리를 찾게 한다.
  • 만약 힙이 비어 있었다면 에러를 리턴한다.

<max 메서드 구현>

	@Override
	public E max() {
		if (isEmpty()) {
			throw new IllegalArgumentException();
		} else {
		return item[0];
			}
		}
  • 힙이 비어 있다면 에러를 리턴하고, 그렇지 않다면 루트 노드의 값을 리턴한다.

<isEmpty 메서드 구현>

	@Override
	public boolean isEmpty() {
		return (size==0);
	}
  • size 변수가 0인지 여부를 리턴한다.

<clear 메서드 구현>

	@Override
	public void clear() {
		item = (E[]) new Comparable [size];
		size=0;
	}
  • item 배열과 size 변수를 초기화한다.

<printAll 메서드 구현>

	@Override
	public void printAll() {
		for (int i=0; i<size; i++) {
			System.out.print(item[i]+" ");
		}
  • 인덱스 0부터 배열 끝까지 순회하며 해당 인덱스의 값을 출력한다.

-전체 코드

public class heap<E> implements heapInterface<E> {
	
	//필드 영역
	private E[] item;
	private int size;
	
	//생성자 영역
	public heap(int arraySize) {
		item = (E[]) new Comparable [arraySize];
		size=0;
	}
	
	//메서드 영역
	
	private void heapifyUp(int i) {
		
		if (i<0 || i>=item.length) {
			throw new IllegalArgumentException();
		} else {
		int parent = (i-1)/2;
		
		if (((Comparable) item[parent]).compareTo(item[i])<0) {
			E tmp = item[i];
			item[i] = item[parent];
			item[parent] = tmp;
			
		heapifyUp(parent);
			}
		}
	}
	
	private void heapifyDown(int i) {
		
		int left = 2*i+1;
		int right = 2*i+2;
		int max = i;
		if (i<0 || i>item.length) {
			throw new IllegalArgumentException();
		} else {
			if (left <=size-1) {
		if (((Comparable) item[max]).compareTo(item[left])<0) {
			max = left;
		} 
		if (((Comparable) item[max]).compareTo(item[right])<0) {
			max = right;
		}
		
		if (max!=i) {
			E tmp = item[i];
			item[i] = item[max];
			item[max] = tmp;
			
			heapifyDown(max); }
			}
		}
	}
	
	@Override
	public void insert(E x) {
		if (size==item.length) {
			throw new IllegalArgumentException();
		} else {
		item[size]=x;
		heapifyUp(size);
		size++;
		}
	}

	@Override
	public void delete() {
		if (isEmpty()) {
			throw new IllegalArgumentException();
		} else {
		item[0]=item[size-1];
		size--;
		heapifyDown(0);
		}
	}

	@Override
	public E max() {
		if (isEmpty()) {
			throw new IllegalArgumentException();
		} else {
		return item[0];
			}
		}

	@Override
	public boolean isEmpty() {
		return (size==0);
	}

	@Override
	public void clear() {
		item = (E[]) new Comparable [size];
		size=0;
	}

	@Override
	public void printAll() {
		for (int i=0; i<size; i++) {
			System.out.print(item[i]+" ");
		}
		
	}

}

 

3) 결과 확인하기

- 코드

public class Test {
	public static void main(String[] args) {
	
		heap<Integer> lst = new heap<>(64);
		
		lst.insert(100);
		
		lst.insert(300);
		
		lst.insert(400);
		
		lst.insert(200);
		
		lst.printAll();
		
		System.out.println();
		
		//100,200,300,400을 차례로 입력하고 출력합니다.
		
		lst.delete();
		
		lst.printAll();
		
		System.out.println();
		
		lst.delete();
		
		lst.printAll();
		
		System.out.println();
		
		//루트에 해당하는 값을 삭제하고 출력합니다.
		
		System.out.println(lst.isEmpty());
		
		System.out.println(lst.max());
		
		//lst가 비었는지 여부와 현재 루트에 있는 값을 출력합니다.
		
		
	}
}

- 결과

1. Queue란?

  • 먼저 들어온 데이터가 먼저 나간다. 즉, 선입선출(FIFO) 방식이다.
  • 맨 앞의 원소에만 접근이 가능하다. 

cf) 연결리스트로 큐 구현하기

연결 리스트로 큐를 구현할 때는 tail에 큐의 마지막 노드를 저장하고 tail이 next로 큐의 첫 번째 값을 참조하게 한다.

 

2. Queue 구현하기

구현할 내용은 아래와 같다.

  • enqueue(E x) - 원소 x를 큐의 끝에 추가한다.
  • dequeue() - 큐의 가장 앞에 있는 원소를 삭제한다.
  • front() - 큐의 가장 앞에 있는 원소를  리턴한다.
  • isEmpty() - 큐가 비어있으면 true, 그렇지 않으면 false를 리턴한다.
  • dequeueAll() - 큐에 있는 모든 원소를 삭제한다.
  • printAll() - 큐의 모든 원소를 순서대로 출력한다.

** 원래 큐에서는 맨 앞의 값에만 접근이 가능하나, 테스트 편의상 printAll() 메서드를 만들어 사용하였다.

 

1) 노드 클래스

public class Node<E> {

	public E item;
	public Node<E> next;
	
	public Node (E newItem) {
		item = newItem;
		next = null;
	}
	
	public Node (E newItem, Node<E> nextNode) {
		item = newItem;
		next = nextNode;
	}
}
  • item 변수에 데이터를 저장한다.
  • next 변수에 다음 노드를 저장한다. next는 다음 노드의 위치를 가리키는 포인터가 된다.
  • 첫 번째 생성자는 데이터를 입력받아 데이터를 item에 저장하고, 포인터 값은 null로 저장한다.
  • 두 번째 생성자는 데이터와 다음 노드를 입력받아 데이터를 item에 저장하고, 포인터 값에 다음 노드를 저장한다.

2) 인터페이스

public interface QueueInterface<E> {

	public void enqueue(E x);
	
	public void dequeue();
	
	public E front();
	
	public boolean isEmpty();
	
	public void dequeueAll();
	
	public void printAll();
	
}

 

3) LinkedQueue 클래스

- 필드 & 생성자 영역

	//필드 영역
	private Node<E> tail;
	int size=0;
	
	//생성자 영역
	public LinkedQueue() {
		tail=null;
	}
  • tail - 큐의 가장 끝 노드를 저장한다. (최근에 들어온 값)
  • size - 큐 원소의 개수를 저장한다.
  • 생성자 - tail에 null 값을 할당한다.

** 사실 위 메서드 중 printAll을 구현하지 않는다면 size는 필요하지 않다.

- 메서드 영역

<enqueue 메서드 구현>

	@Override
	public void enqueue(E x) {
		Node<E> newNode = new Node<>(x);
		if (isEmpty()) {
			tail = newNode;
			tail.next=tail;
		} else {
			newNode.next=tail.next;
			tail.next=newNode;
			tail=newNode;
		}
		size++;
	}
  • 입력 받은 값 x를 가지는 새 노드를 생성한다.
  • 만약 첫 노드라면 tail에 newNode를 입력하고 tail이 next로 스스로를 참조하게 한다.
  • 그렇지 않다면 새 노드가 front를 참조하고, tail이 새 노드를 참조하게 한다.
  • tail 값에 newNode를 입력한다.

<dequeue 메서드 구현>

	@Override
	public void dequeue() {
		if (isEmpty()) {
			System.out.println("ERROR");
		} else {
			if (tail.next==tail) {
				tail = null; 
			} else {
				tail.next=tail.next.next;
			}
		}
		size--;
	}
  • 만약 큐가 비어 있다면 에러를 출력한다.
  • 만약 큐에 원소가 한 개라면 tail 값에 null을 입력한다.
  • 큐에 원소가 여러 개라면 tail이 next로 기존 front값을 생략하고 그 다음 노드를 참조하게 한다.

<front 메서드 구현>

	@Override
	public E front() {
		if (isEmpty()) {
			return null;
		} else {
		return tail.next.item;
		}
	}
  • 만약 큐가 비어 있다면 null을 리턴한다.
  • 그렇지 않다면 front의 item값을 출력한다.

<isEmpty 메서드 구현>

	@Override
	public boolean isEmpty() {
		return (tail==null);
	}
  • tail==null 이면 true, 아니면 false를 리턴한다.

<dequeueAll 메서드 구현>

	@Override
	public void dequeueAll() {
		tail=null;
		size=0;
	}
  • tail과 size를 초기화한다.

<printAll 메서드 구현>

	@Override
	public void printAll() {
		Node<E> currNode=tail;
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			System.out.print(currNode.item+" ");
		}
	}
  • front부터 size만큼 큐를 순회하며 해당 노드의 item값을 출력한다.

- 전체 코드

public class LinkedQueue<E> implements QueueInterface<E> {

	//필드 영역
	private Node<E> tail;
	int size=0;
	
	//생성자 영역
	public LinkedQueue() {
		tail=null;
	}
	
	//메서드 영역
	
	@Override
	public void enqueue(E x) {
		Node<E> newNode = new Node<>(x);
		if (isEmpty()) {
			tail = newNode;
			tail.next=tail;
		} else {
			newNode.next=tail.next;
			tail.next=newNode;
			tail=newNode;
		}
		size++;
	}

	@Override
	public void dequeue() {
		if (isEmpty()) {
			System.out.println("ERROR");
		} else {
			if (tail.next==tail) {
				tail = null; 
			} else {
				tail.next=tail.next.next;
			}
		}
		size--;
	}

	@Override
	public E front() {
		if (isEmpty()) {
			return null;
		} else {
		return tail.next.item;
		}
	}

	@Override
	public boolean isEmpty() {
		return (tail==null);
	}

	@Override
	public void dequeueAll() {
		tail=null;
		size=0;
		
	}
	
	@Override
	public void printAll() {
		Node<E> currNode=tail;
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			System.out.print(currNode.item+" ");
		}
	}

}

 

3) 결과 확인하기

<코드>

public class Test {
	public static void main(String[] args) {
		
	
	LinkedQueue<Integer> lst = new LinkedQueue<>();
	
	lst.enqueue(100);
	
	lst.enqueue(200);
	
	lst.enqueue(300);
	
	lst.enqueue(400);

	lst.printAll();
	System.out.println();

	//LinkedQueue에 100,200,300,400을 순서대로 입력 후 현재 ArrayQueue 상태를 출력
	
	System.out.println(lst.front());
	
	//LinkedQueue의 가장 앞에 있는 값을 출력
	
	lst.dequeue();
	
	lst.printAll();
	
	System.out.println();
	
	//LinkedQueue에 100,200,300,400을 순서대로 입력 후 현재 ArrayQueue 상태를 출력
	
	System.out.println(lst.isEmpty());
	
	//LinkedQueue가 비었는지 출력
	
	
	}
}

<결과>

1.Queue란?

  • 먼저 들어온 데이터가 먼저 나간다. 즉, 선입선출(FIFO) 방식이다.
  • 맨 앞의 원소에만 접근이 가능하다. 

2. Queue 구현하기

구현할 내용은 아래와 같다.

  • enqueue(E x) - 원소 x를 큐의 끝에 추가한다.
  • dequeue() - 큐의 가장 앞에 있는 원소를 삭제한다.
  • front() - 큐의 가장 앞에 있는 원소를  리턴한다.
  • isEmpty() - 큐가 비어있으면 true, 그렇지 않으면 false를 리턴한다.
  • dequeueAll() - 큐에 있는 모든 원소를 삭제한다.
  • printAll() - 큐의 모든 원소를 순서대로 출력한다.

** 원래 큐에서는 맨 앞의 값에만 접근이 가능하나, 테스트 편의상 printAll() 메서드를 만들어 사용하였다.

 

1) 인터페이스

public interface QueueInterface<E> {

	public void enqueue(E x);
	
	public void dequeue();
	
	public E front();
	
	public boolean isEmpty();
	
	public void dequeueAll();
	
	public void printAll();
	
}

 

2) 클래스

- 필드 영역

private E queue[];
private int front, tail, size;
private static final int DEFAULT_CAPACITY = 64;
  • queue[] - ArrayQueue의 원소를 받을 배열 리스트이다.
  • front, tail - ArrayQueue의 맨 첫 원소, 마지막 원소를 표시할 포인터이다.
  • size -  ArrayQueue의 원소 개수를 저장할 변수이다.
  • DEFAULT_CAPACITY - ArrayQueue 생성시 크기를 정해주지 않으면 해당 크기로 생성한다.

- 생성자 영역

	public ArrayQueue() {
		queue = (E[]) new Object[DEFAULT_CAPACITY];
		front = 0;
		tail = DEFAULT_CAPACITY-1;
		size=0;
	}
	
	public ArrayQueue(int n) {
		queue = (E[]) new Object[n];
		front = 0;
		tail = DEFAULT_CAPACITY-1;
		size=0;
	}
  • 입력 값으로 정수를 받으면 해당 정수 크기의 큐를, 그렇지 않으면 DEFAULT_CAPACITY 크기의 큐를 생성한다.

- 메서드 영역

<is Full 메서드 구현>

public boolean isFull() {
		return (size==queue.length);
	}
  • 인터페이스의 메서드를 오버라이딩 한 것은 아니다.
  • 현재 size 변수(원소의 개수)와 생성시 배정한 queue의 크기가 같으면 true, 아니면 false를 리턴한다.

<enqueue 메서드 구현>

	@Override
	public void enqueue(E x) {
		if (isFull()) {
			System.out.println("ERROR");
		} else {
			tail = (tail+1)%queue.length;
			queue[tail]=x;
			size++;
		}
	}
  • 만약 큐에 빈 공간이 없으면 에러를 출력한다.
  • 그렇지 않다면 tail에 1을 더해주고 해당 위치에 입력받은 값을 입력한다.
  • queue.length로 나눠주는 이유는 tail 포인터가 큐의 끝까지 도달했을 때 맨 앞 위치로 돌려놓기 위해서이다.
  • size 변수를 1 증가시킨다.

<dequeue 메서드 구현>

	@Override
	public void dequeue() {
		if (isEmpty()) {
			System.out.println("ERROR");
		}
		queue[front]=null;
		front=(front+1)%queue.length;
		size--;
	}
  • 만약 큐가 비어 있으면 에러를 출력한다.
  • 그렇지 않다면 현재 front가 가리키는 위치의 값을 null로 비워준다.
  • front에 1을 더해 포인터를 뒤로 이동시킨다.
  • queue.length로 나눠주는 이유는 front 포인터가 큐의 끝까지 도달했을 때 맨 앞 위치로 돌려놓기 위해서이다.

<front 메서드 구현>

	@Override
	public E front() {
		if (isEmpty()) {
			return null;
		} else {
		return queue[front];
		}
	}
  • 만약 큐가 비어 있으면 null값을 리턴한다.
  • 그렇지 않다면 현재 front가 가리키는 위치의 값을 리턴한다.

<isEmpty 메서드 구현>

	@Override
	public boolean isEmpty() {
		return (size==0);
	}
  • 원소의 개수가 0이면 true, 아니면 false를 리턴한다.

<dequeueAll 메서드 구현>

	@Override
	public void dequeueAll() {
		queue = (E[]) new Object[queue.length];
		front = 0;
		tail = DEFAULT_CAPACITY-1;
		size=0;
	}
  • queue, front, tail, size값을 초기화한다.

<printAll 메서드 구현>

	@Override
	public void printAll() {
		for (int i=tail; i>=front; i--) {
			System.out.print(queue[i]+" ");
		}
	}
  • tail부터 front까지 순회하며 큐 원소 값을 출력한다.

- 전체 코드

public class ArrayQueue<E> implements QueueInterface<E> {

	
	//필드 영역
	private E queue[];
	
	private int front, tail, size;
	
	private static final int DEFAULT_CAPACITY = 64;
	
	//생성자 영역
	public ArrayQueue() {
		queue = (E[]) new Object[DEFAULT_CAPACITY];
		front = 0;
		tail = DEFAULT_CAPACITY-1;
		size=0;
	}
	
	public ArrayQueue(int n) {
		queue = (E[]) new Object[n];
		front = 0;
		tail = DEFAULT_CAPACITY-1;
		size=0;
	}
	
	//메서드
	
	public boolean isFull() {
		return (size==queue.length);
		
	}

	@Override
	public void enqueue(E x) {
		if (isFull()) {
			System.out.println("ERROR");
		} else {
			tail = (tail+1)%queue.length;
			queue[tail]=x;
			size++;
		}
	}

	@Override
	public void dequeue() {
		if (isEmpty()) {
			System.out.println("ERROR");
		} else {
		queue[front]=null;
		front=(front+1)%queue.length;
		size--;
		}
	}

	@Override
	public E front() {
		if (isEmpty()) {
			return null;
		} else {
		return queue[front];
		}
	}

	@Override
	public boolean isEmpty() {
		return (size==0);
	}

	@Override
	public void dequeueAll() {
		queue = (E[]) new Object[queue.length];
		front = 0;
		tail = DEFAULT_CAPACITY-1;
		size=0;
	}

	@Override
	public void printAll() {
		for (int i=tail; i>=front; i--) {
			System.out.print(queue[i]+" ");
		}
	}
}

 

3) 결과 확인하기

- 코드

public class Test {
	public static void main(String[] args) {
		
	
	ArrayQueue<Integer> lst = new ArrayQueue<>();
	
	lst.enqueue(100);
	
	lst.enqueue(200);
	
	lst.enqueue(300);
	
	lst.enqueue(400);
	
	lst.printAll();
	System.out.println();
	
	//ArrayQueue에 100,200,300,400을 순서대로 입력 후 현재 ArrayQueue 상태를 출력
	
	System.out.println(lst.front());
	
	//ArrayQueue의 가장 앞에 있는 값을 출력
	
	lst.dequeue();
	
	lst.printAll();
	
	System.out.println();
	
	//ArrayQueue의 가장 앞에 있는 값을 삭제하고 출력
	
	System.out.println(lst.isEmpty());
	
	//ArrayQueue가 비었는지 출력
	
	
	}
}

- 결과

1. Stack이란?

스택

  • 마지막에 들어온 데이터가 가장 먼저 나간다. 즉, 후입선출(LIFO) 방식이다.
  • 맨 위의 원소에만 접근이 가능하다. 
  • 가상 메모리의 스택 영역에서 이와 같은 자료 구조를 이용한다.

cf) 연결리스트로 스택 구현하기

연결리스트로 스택을 구현은 위처럼 topNode를 두고 새로운 데이터가 추가될 때마다 해당 노드를 topNode로 설정한 뒤 이전 노드를 참조하는 방식으로 이루어진다. 삭제할 땐 topNode.next를 topNode로 설정해주면 된다.

 

2. Stack 구현하기

구현할 내용은 아래와 같다.

  • push(E x) - 원소 x를 스택에 입력한다.
  • pop() - 스택 맨 위의 원소를 삭제한다.
  • top() - 스택 맨 위의 원소 값을 리턴한다.
  • isEmpty() - 스택이 비었는지 여부를 리턴한다.
  • popAll() - 스택의 내부 데이터를 모두 삭제한다.
  • printAll() - 스택의 내부의 모든 데이터를 출력한다.

** 원래 스택에서는 맨 위의 값에만 접근이 가능하나, 테스트 편의상 printAll() 메서드를 만들어 사용하였다.

 

1) 노드 클래스

public class Node<E> {

	public E item;
	public Node<E> next;
	
	public Node (E newItem) {
		item = newItem;
		next = null;
	}
	
	public Node (E newItem, Node<E> nextNode) {
		item = newItem;
		next = nextNode;
	}
}
  • item 변수에 데이터를 저장한다.
  • next 변수에 다음 노드를 저장한다. next는 다음 노드의 위치를 가리키는 포인터가 된다.
  • 첫 번째 생성자는 데이터를 입력받아 데이터를 item에 저장하고, 포인터 값은 null로 저장한다.
  • 두 번째 생성자는 데이터와 다음 노드를 입력받아 데이터를 item에 저장하고, 포인터 값에 다음 노드를 저장한다.

 

2) 인터페이스

public interface StackInterface<E> {

	public void push (E x);
	
	public void pop();
	
	public E top();
	
	public boolean isEmpty();
	
	public void popAll();
	
	public void printAll();
	
}

 

3) LinkedStack 클래스

<push 메서드 구현>

@Override
	public void push(E x) {
		topNode = new Node<> (x, topNode);
		
	}
  • 기존 topNode를 다음 노드로 참조하고 입력 받은 값을 item 값으로 하는 새 노드를 만든다.

<pop 메서드 구현>

@Override
	public void pop() {
		if (isEmpty()) {
			System.out.println("ERROR");
	} else {
		topNode = topNode.next;
		}
	}
  • 스택이 비었다면 에러를 출력한다.
  • 그렇지 않다면 기존 topNode 자리에 기존 topNode가 참조하던 다음 노드가 들어오게 한다.

<top 메서드 구현>

@Override
	public E top() {
		if (isEmpty()) {
			return null;
		} else {
			return topNode.item;
		}
	}
  • 스택이 비었다면 null 값을 리턴한다.
  • 그렇지 않다면 topNode의 item값을 리턴한다.

<isEmpty 메서드 구현>

@Override
	public boolean isEmpty() {
		return topNode==null;
	}
  • topNode가 null값인지 여부를 리턴한다.

<popAll 메서드 구현>

@Override
	public void popAll() {
		topNode = null;	
	}
  • topNode를 null로 초기화한다.

<printAll 메서드 구현>

@Override
	public void printAll() {
		Node<E> currNode = topNode;
		while (currNode!=null) {
				System.out.print(currNode.item+" ");
				currNode=currNode.next;
		}
  • for문이 topNode부터 스택의 전체 노드를 순회하며 값을 프린트한다.

- 전체 코드

public class LinkedStack<E> implements StackInterface<E>{

	private Node<E> topNode;
	
	public LinkedStack() {
		topNode=null;
	}
	
	@Override
	public void push(E x) {
		topNode = new Node<> (x, topNode);
		
	}

	@Override
	public void pop() {
		if (isEmpty()) {
			System.out.println("ERROR");
	} else {
		topNode = topNode.next;
		}
	}
	
	@Override
	public E top() {
		if (isEmpty()) {
			return null;
		} else {
			return topNode.item;
		}
	}

	@Override
	public boolean isEmpty() {
		return topNode==null;
	}

	@Override
	public void popAll() {
		topNode = null;
		
	}

	@Override
	public void printAll() {
		Node<E> currNode = topNode;
		while (currNode!=null) {
				System.out.print(currNode.item+" ");
				currNode=currNode.next;
		}
		
	}

}

** 배열 리스트로 구현한 스택과 달리 정해진 용량이 없기 때문에 isFull 메서드는 필요하지 않다.

 

3) 결과 확인하기

<코드>

public class Test {
	public static void main(String[] args) {

	LinkedStack<Integer> stack = new LinkedStack<>();
	
	stack.push(100);
	
	stack.push(200);
	
	stack.push(300);
	
	stack.push(400);
	
	System.out.println(stack.top());
	
	stack.printAll();
	System.out.println();
	
	
	for (int i=0; i<5; i++) {
		stack.pop();
	}
	
	stack.printAll();
	
	//스택에서 pop을 5번 수행한 뒤 출력합니다. - 원소가 4개인데 5번 pop하였으므로 에러가 발생하고, 스택이 비어있으므로 내용물은 출력되지 않음.
	
	System.out.println(stack.isEmpty());
	
	// 스택이 비었는지 여부를 출력합니다.
	
	stack.push(100);
	stack.printAll();
	System.out.println();
	
	//stack에 100을 입력하고 출력합니다.
	
	stack.popAll();
	stack.printAll();
	
	//stack에 - stack을 popAll 했으므로 내용물이 출력되지 않음.
	
	}
}

<결과>

1. Stack이란?

스택

  • 마지막에 들어온 데이터가 가장 먼저 나간다. 즉, 후입선출(LIFO) 방식이다.
  • 맨 위의 원소에만 접근이 가능하다. 
  • 가상 메모리의 스택 영역에서 이와 같은 자료 구조를 이용한다.

2. Stack 구현하기

구현할 내용은 아래와 같다.

  • push(E x) - 원소 x를 스택에 입력한다.
  • pop() - 스택 맨 위의 원소를 삭제한다.
  • top() - 스택 맨 위의 원소 값을 리턴한다.
  • isEmpty() - 스택이 비었는지 여부를 리턴한다.
  • popAll() - 스택의 내부 데이터를 모두 삭제한다.
  • printAll() - 스택의 내부의 모든 데이터를 출력한다.

** 원래 스택에서는 맨 위의 값에만 접근이 가능하나, 테스트 편의상 printAll() 메서드를 만들어 사용하였다.

 

1) 인터페이스

public interface StackInterface<E> {

	public void push (E x);
	
	public void pop();
	
	public E top();
	
	public boolean isEmpty();
	
	public void popAll();
	
	public void printAll();
	
}

 

2) 클래스

- 필드 영역

//필드 영역
	private E stack[];
	private int topIndex;
	private static final int DEFAULT_CAPACITY = 64;
  • stack - 원소를 입력 받을 배열 리스트이다.
  • topIndex - 스택의 맨 윗 원소의 인덱스를 저장한다.
  • DEFAULT_CAPACITY - 스택 생성 시 용량을 따로 선언하지 않을 경우 설정할 기본 용량이다.

- 생성자 영역

//생성자 영역
	public ArrayStack() {
		stack = (E[]) new Object[DEFAULT_CAPACITY];
		topIndex = -1;
	}
	
	public ArrayStack(int n) {
		stack = (E[]) new Object[n];
		topIndex = -1;
	}
  • 생성 시 입력 값으로 정수를 받으면 해당 정수 크기의 스택을, 그렇지 않으면 DEFAULT_CAPACITY 크기의 스택을 생성한다.

-메서드 영역

<push 메서드 구현>

//메서드 영역
	@Override
	public void push(E x) {
		if (isFull()) {
			System.out.println("ERROR");
		} else {
			topIndex++;
			stack[topIndex]=x;
		}
	}
  • 만약 stack에 남은 공간이 없는데 push를 시도하면 에러를 출력한다.
  • 그렇지 않으면 topIndex를 늘려준 뒤 해당 인덱스에 입력받은 데이터를 입력한다.

<pop 메서드 구현>

@Override
	public void pop() {
		if (isEmpty()) {
			System.out.println("ERROR");
		} else {
		stack[topIndex]=null;
		topIndex--;
		}
	}
  • 만약 stack이 비어 있는데 pop을 시도하면 에러를 출력한다.
  • 그렇지 않다면 현재 topIndex를 null로 바꾼 후 topIndex를 1 감소시킨다.
  • ** 사실 add시 해당 인덱스를 덮어 쓸 것이기 때문에 topIndex만 1 감소시키고 null 처리는 해주지 않아도 된다.

<top 메서드 구현>

@Override
	public E top() {
		if (isEmpty()) {
			return null;
		} else {
		return stack[topIndex];
		}
	}
  • 만약 스택이 비어있는데 top을 시도하면 null 값을 반환한다.
  • 그렇지 않다면 stack의 topIndex값을 반환한다.

<isEmpty&isFull 메서드 구현>

@Override
	public boolean isEmpty() {
		return (topIndex<0);
	}

//isFull메서드는 인터페이스를 오버라이딩 한 것이 아님에 주의!
	public boolean isFull() {
		return (topIndex==stack.length-1);
	}
  • isEmpty - topIndex가 음수이면 true, 아니면 false.
  • isFull - topIndex와 스택의 크기를 비교하여 스택이 다 찼는지를 판별.

<popAll 메서드 구현>

@Override
	public void popAll() {
		stack = (E[]) new Object[stack.length];
		topIndex=-1;
	}
  • stack을 기존 길이의 빈 배열 리스트로 덮어쓴다.
  • topIndex를 초기화한다.

<printAll 메서드 구현>

@Override
	public void printAll() {
		
		for (int i=0; i<=topIndex; i++) {
			System.out.print(stack[i]+" ");
		}
		
	}
  • for문이 전체 스택의 인덱스를 순회하며 해당 인덱스의 값들을 프린트한다.

- 전체 코드

public class ArrayStack<E> implements StackInterface<E> {
	
	//필드 영역
	private E stack[];
	private int topIndex;
	private static final int DEFAULT_CAPACITY = 64;
	
	//생성자 영역
	public ArrayStack() {
		stack = (E[]) new Object[DEFAULT_CAPACITY];
		topIndex = -1;
	}
	
	public ArrayStack(int n) {
		stack = (E[]) new Object[n];
		topIndex = -1;
	}
	
	//메서드 영역
	@Override
	public void push(E x) {
		if (isFull()) {
			System.out.println("ERROR");
		} else {
			topIndex++;
			stack[topIndex]=x;
		}
	}

	@Override
	public void pop() {
		if (isEmpty()) {
			System.out.println("ERROR");
		} else {
		stack[topIndex]=null; //안해도 됨.
		topIndex--;
		}
	}

	@Override
	public E top() {
		if (isEmpty()) {
			return null;
		} else {
		return stack[topIndex];
		}
	}

	@Override
	public boolean isEmpty() {
		return (topIndex<0);
	}

	public boolean isFull() {
		return (topIndex==stack.length-1);
	}

	@Override
	public void popAll() {
		stack = (E[]) new Object[stack.length];
		topIndex=-1;
	}

	@Override
	public void printAll() {
		
		for (int i=0; i<=topIndex; i++) {
			System.out.print(stack[i]+" ");
		}
		
	}

}

3) 결과 확인하기

<코드>

public class Test {
	public static void main(String[] args) {

	ArrayStack<Integer> stack = new ArrayStack<>();
	
	stack.push(100);
	
	stack.push(200);
	
	stack.push(300);
	
	stack.push(400);
	
	System.out.println(stack.top());
	
	stack.printAll();
	System.out.println();
	
	
	
	for (int i=0; i<5; i++) {
		stack.pop();
	}
	
	stack.printAll();
	
	//스택에서 pop을 5번 수행한 뒤 출력합니다. - 원소가 4개인데 5번 pop하였으므로 에러가 발생하고, 스택이 비어있으므로 내용물은 출력되지 않음.
	
	System.out.println(stack.isEmpty());
	
	// 스택이 비었는지 여부를 출력합니다.
	
	stack.push(100);
	stack.printAll();
	System.out.println();
	
	//stack에 100을 입력하고 출력합니다.
	
	stack.popAll();
	stack.printAll();
	
	//stack에 - stack을 popAll 했으므로 내용물이 출력되지 않음.
	
	}
}

<결과>

정상적으로 출력된다.

1. CircularDoublyLinkedList란?

  • 단일 방향 연결 리스트와 다르게 어떤 노드를 한 번 지나도 반대 방향으로 돌아갈 수 있다.
  • 마지막 노드의 next 값이 헤드 노드를 가리키고 헤드 노드의 prev 값이 마지막 노드를 가리킨다.

2. CircularDoublyLinkedList 구현하기

구현할 내용은 아래와 같다.

  • add (int i, E x) - i번째 인덱스에 원소 x를 입력한다.
  • append (E x) - 배열 리스트의 끝에 원소 x를 입력한다.
  • remove (int i) - i번째 원소를 삭제한다.
  • removeItem (E x) - 배열 리스트에서 원소 x를 삭제한다. 중복 원소가 있을 경우 먼저 나오는 원소를 삭제한다.
  • get (int i) - i번째 원소를 리턴한다.
  • set (int i, E x) - i번째 인덱스의 원소를 x로 교체한다.
  • len() - 배열 리스트의 원소 개수를 리턴한다.
  • isEmpty() - 배열 리스트가 비었는지 여부를 boolean으로 리턴한다.
  • clear() - 인덱스를 비운다.
  • indexOf(E x) - 배열 리스트 원소 x의 인덱스를 리턴한다. 중복 원소가 있을 경우 먼저 나오는 원소 인덱스를 리턴한다.
  • printAll() - 배열 리스트의 모든 원소를 리턴한다.

1) 노드 클래스

public class BidirectionalNode<E> {
	
	public BidirectionalNode<E> prev;
	public E item;
	public BidirectionalNode<E> next;
	
	public BidirectionalNode() {
		prev=next=null;
		item=null;
	}
	
	public BidirectionalNode (E newItem) {
		prev=next=null;
		item = newItem;
	}
	
	public BidirectionalNode (BidirectionalNode<E> prevNode, E newItem, BidirectionalNode<E> nextNode) {
		prev = prevNode;
		item = newItem;
		next = nextNode;
	}
}
  • prev 변수에 해당 노드의 이전 노드를 저장한다.
  • item 변수에 해당 노드에 입력할 데이터 값을 저장한다.
  • next 변수에 해당 노드의 다음 노드를 저장한다.
  • 첫 번째 생성자 - 클래스 호출 시 입력값이 없으면 prev, next, item 변수를 모두 null로 저장한다.
  • 두 번째 생성자 - 클래스 호출 시 노드에 저장할 데이터를 입력받아 item 변수에 저장한다.
  • 세 번째 생성자 - 클래스 호출 시 이전 노드, 저장할 값, 다음 노드를 입력받아 각각 prev, item, next 변수에 저장한다.

2) 인터페이스

public interface ListInterface<E>{
	
	public void add(int i, E x); 
    
	public void append (E x); 
	
	public void remove (int i); 
	
	public void removeItem (E x); 
	
	public E get(int i); 
	
	public void set(int i, E x); 
    
	public int len(); 
	
	public boolean isEmpty(); 
	
	public void clear(); 
    
	public int IndexOf (E x); 
    
	public void printAll ();
	
}

 

3) CircularDoublyLinkedList 클래스

- 필드 영역

//필드 영역
	private BidirectionalNode<E> head;
	private int size;
  • 더미 헤드를 저장할 head 변수를 선언한다.
  • 원형 양방향 연결 리스트의 원소 개수를 저장할 size 변수를 선언한다.

- 생성자 영역

//생성자 영역
	public CircularDoublyLinkedList() {
	head = new BidirectionalNode<>(null);
	head.next = head.prev = head;
	}
  • head 변수에 item 값이 null인 노드를 입력합니다.
  • 처음 원형 양방향 연결 리스트가 생성 되었을 때 head 노드는  next와 prev로 자기 자신을 참조합니다.

- 메서드 영역

<getNode 메서드 구현>

public BidirectionalNode<E> getNode (int i) {
		if (i>size||i<-1) throw new IllegalArgumentException();
		BidirectionalNode<E> currNode = head;
		
		if (i<size/2) {
			for (int j=0; j<=i; j++) {
				currNode=currNode.next;
			}
		} else {
				for (int j=size-1; j>=i; j--) {
					currNode=currNode.prev;
				}
			}
		return currNode;
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우 (-1일 경우 더미헤드를 반환)
  • currNode 변수에 head 노드를 저장한다.
  • 입력 받은 인덱스가 앞쪽에 있으면 head 노드부터 입력 받은 인덱스까지 next로 이동하고 currNode를 리턴한다.
  • 입력 받은 인덱스가 뒷쪽에 있으면 head 노드부터 입력 받은 인덱스까지 prev로 이동하고 currNode를 리턴한다.

<add 메서드 구현>

	@Override
	public void add(int i, E x) {
		if (i>size||i<0) throw new IllegalArgumentException();
		Node<E> prevNode = getNode(i-1);
		Node<E> currNode = new Node(x,prevNode.next);
		prevNode.next=currNode;
		
		if (i==size) {
			tail = currNode;
		}
		
		size++;
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • prevNode 변수에 입력 받은 인덱스의 이전 인덱스 노드를 저장한다.
  • prevNode는 새로 들어올 노드의 이전 노드가 될 것이다.
  • nextNode 변수에 입력 받은 인덱스의 노드를 저장한다.
  • nextNode는 새로 들어올 노드의 다음 노드가 될 것이다.
  • newNode 변수에 입력 받은 데이터를 item 값으로 하고 prevNode를 이전 노드, nextNode를 다음 노드로 하는 노드를 저장한다.
  • prevNode의 다음 노드를 newNode로, nextNode의 이전 노드를 newNode로 갱신해준다.
  • size 변수를 1 증가시킨다.

<append 메서드 구현>

@Override
	public void append(E x) {
		
		BidirectionalNode<E> prevNode = getNode(size-1);
		BidirectionalNode<E> newNode = new BidirectionalNode<>(prevNode,x,head);
		
		prevNode.next=newNode;
		head.prev=newNode;
		
		size++;
		
	}
  • prevNode에 현재 원형 양방향 연결 리스트의 마지막 인덱스 노드를 저장한다.
  • NewNode 변수에 prevNode를 이전 노드로 하고 head 노드를 다음 노드로 하며 입력 받은 데이터를 item 값으로 하는 노드를 저장한다.
  • prevNode의 다음 노드를 newNode로 저장한다.
  • head 노드의 이전 도르르 newNode로 저장한다.
  • size 변수를 1 증가시킨다.

<remove 메서드 구현>

	@Override
	public void remove(int i) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		
		BidirectionalNode<E> prevNode = getNode(i-1);
		BidirectionalNode<E> nextNode = getNode(i+1);
		
		prevNode.next=nextNode;
		nextNode.prev=prevNode;
		
		size--;
		
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • prevNode 변수에 입력 받은 인덱스의 이전 인덱스 노드를 저장한다.
  • nextNode 변수에 입력 받은 인덱스의 다음 인덱스 노드를 저장한다.
  • prevNode가 next로 i번째 노드를 생략하고 nextNode를 참조하게 한다.
  • nextNode가 prev로 i번째 노드를 생략하고 prevNode를 참조하게 한다.
  • size 변수를 1 감소시킨다.

<removeItem 메서드 구현>

@Override
	public void removeItem(E x) {

		BidirectionalNode<E> currNode = head;
		
		
		for (int i=0; i<size; i++) {
			
			currNode=currNode.next;
			
			if (currNode.item.equals(x)) {
				
				currNode.prev.next=currNode.next;
				currNode.next.prev=currNode.prev;
				
				break;
			}
		}
		
		size--;
		
	}
  • currNode 변수에 head 노드를 저장한다.
  • 0부터 마지막 인덱스까지 순회하면서 입력받은 x와 item 값이 같은 노드가 있는지 검사한다.
  • currNode와 x 값이 일치하면 해당 노드의 이전 노드가 next로 currNode를 생략하고 그 다음 노드를 참조하게 한다. 해당 노드의 다음 노드는 prev 값으로 currNode를 생략하고 그 이전 노드를 참조하게 한다.
  • size 변수를 1 감소시킨다.

<get,set,len,isEmpty 메서드 구현>

	@Override
	public E get(int i) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		return getNode(i).item;
	}

	@Override
	public void set(int i, E x) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		getNode(i).item=x;
		
	}

	@Override
	public int len() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return (size==0);
	}
  • get - 입력받은 인덱스에 해당하는 값을 리턴한다.
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • set - 입력받은 인덱스에 해당하는 값을 입력받은 값으로 교체한다.
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • len - size 변수를 리턴한다.
  • isEmpty - size==0 여부를 리턴한다.

<clear 메서드 구현>

@Override
	public void clear() {
		
		size=0;
		head.next=head;
		head.prev=head;
		
	}
  • size 변수를 0으로 초기화한다.
  • head 노드가 next, prev로 자기 자신을 참조하게 한다.

<indexOf 메서드 구현>

@Override
	public int IndexOf(E x) {
		
		int idx = -1;
		BidirectionalNode<E> currNode = head;
		
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			if (currNode.item.equals(x)) {
				idx=i;
				break;
			}
		}
		
		return idx;
	}
  • for문으로 연결 리스트를 순회하며 노드를 변수 currNode에 저장한다.
  • currNode.item과 입력받은 x를 비교한다.
  • currNode.item==x일때의 인덱스를 리턴한다.
  • 만약 x와 같은 값이 없으면 -1를 리턴한다.

<printAll 메서드 구현>

@Override
	public void printAll() {
		
		BidirectionalNode<E> currNode = head;
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			System.out.print(currNode.item+" ");
		}
		
		System.out.println();
		
	}
  • for문으로 연결 리스트를 순회하며 노드를 변수 currNode에 저장한다.
  • 매 순회마다 currNode.item을 출력한다.

- 전체 코드

public class CircularDoublyLinkedList<E> implements ListInterface<E>{
	
	//필드 영역
	private BidirectionalNode<E> head;
	private int size;
	
	//생성자 영역
	public CircularDoublyLinkedList() {
	head = new BidirectionalNode<>(null);
	head.next = head.prev = head;
	}
	
	//메서드 영역
	public BidirectionalNode<E> getNode (int i) {
		if (i>size||i<-1) throw new IllegalArgumentException();
		BidirectionalNode<E> currNode = head;
		
		if (i<size/2) {
			for (int j=0; j<=i; j++) {
				currNode=currNode.next;
			}
		} else {
				for (int j=size-1; j>=i; j--) {
					currNode=currNode.prev;
				}
			}
		return currNode;
	}
	
	@Override
	public void add(int i, E x) {
		if (i>size||i<0) throw new IllegalArgumentException();
		
		BidirectionalNode<E> prevNode = getNode(i-1);
		BidirectionalNode<E> nextNode = getNode(i);
		BidirectionalNode<E> newNode = new BidirectionalNode<>(prevNode,x,nextNode);
		
		prevNode.next=newNode;
		nextNode.prev=newNode;
		
		size++;
	}

	@Override
	public void append(E x) {
		
		BidirectionalNode<E> prevNode = getNode(size-1);
		BidirectionalNode<E> newNode = new BidirectionalNode<>(prevNode,x,head);
		
		prevNode.next=newNode;
		head.prev=newNode;
		
		size++;
		
	}

	@Override
	public void remove(int i) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		
		BidirectionalNode<E> prevNode = getNode(i-1);
		BidirectionalNode<E> nextNode = getNode(i+1);
		
		prevNode.next=nextNode;
		nextNode.prev=prevNode;
		
		size--;
		
	}

	@Override
	public void removeItem(E x) {

		BidirectionalNode<E> currNode = head;
		
		
		for (int i=0; i<size; i++) {
			
			currNode=currNode.next;
			
			if (currNode.item.equals(x)) {
				
				currNode.prev.next=currNode.next;
				currNode.next.prev=currNode.prev;
				
				break;
			}
		}
		
		size--;
		
	}

	@Override
	public E get(int i) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		return getNode(i).item;
	}

	@Override
	public void set(int i, E x) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		getNode(i).item=x;
		
	}

	@Override
	public int len() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return (size==0);
	}

	@Override
	public void clear() {
		
		size=0;
		head.next=head;
		head.prev=head;
		
	}

	@Override
	public int IndexOf(E x) {
		
		int idx = -1;
		BidirectionalNode<E> currNode = head;
		
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			if (currNode.item.equals(x)) {
				idx=i;
				break;
			}
		}
		
		return idx;
	}

	@Override
	public void printAll() {
		
		BidirectionalNode<E> currNode = head;
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			System.out.print(currNode.item+" ");
		}
		
		System.out.println();
		
	}

}

 

3) 결과 확인하기

<코드>

public class Test {
	public static void main(String[] args) {

	CircularLinkedList<Integer> lst = new CircularLinkedList<>();
	
	lst.add(0, 100);
	
	lst.add(1, 200);
	
	lst.add(2, 300);
	
	lst.append(400);
	
	lst.printAll();
	
	//100,200,300,400을 차례로 입력하고 출력합니다.
	
	lst.remove(0);
	
	lst.printAll();
	
	lst.removeItem(400);
	
	lst.printAll();
	
	//0번째 인덱스의 값과 내용물이 400인 값을 삭제하고 출력합니다.
	
	lst.IndexOf(300);
	
	//300의 인덱스를 출력합니다.
	
	System.out.println(lst.isEmpty());
	System.out.println(lst.len());
	
	//lst가 비었는지 여부와 lst의 길이를 출력합니다.
	
	System.out.println(lst.get(0));
	
	//인덱스 0의 값을 출력합니다.
	
	lst.set(0, 1000);
	
	lst.printAll();
	
	//인덱스 0의 값을 1000으로 바꾸고 출력합니다.
	
	}
}

<결과>

정상적으로 출력된다.

1. CircularLinkedList란?

원형 연결 리스트

  • LinkedList는 리스트의 맨 끝에 데이터를 입력/삭제시 head부터 끝까지 이동해야 하므로 O(n)의 시간이 든다.
  • CircularLinkedList는 맨 끝의 노드가 헤드 노드를 참조하도록 하여 시간을 단축한다.
  • tail에는 유효한 값이 들어갈 수 있지만 tail.next는 item에 null값을 유지한다.

2. CircularLinkedList 구현하기

구현할 내용은 아래와 같다.

  • add (int i, E x) - i번째 인덱스에 원소 x를 입력한다.
  • append (E x) - 배열 리스트의 끝에 원소 x를 입력한다.
  • remove (int i) - i번째 원소를 삭제한다.
  • removeItem (E x) - 배열 리스트에서 원소 x를 삭제한다. 중복 원소가 있을 경우 먼저 나오는 원소를 삭제한다.
  • get (int i) - i번째 원소를 리턴한다.
  • set (int i, E x) - i번째 인덱스의 원소를 x로 교체한다.
  • len() - 배열 리스트의 원소 개수를 리턴한다.
  • isEmpty() - 배열 리스트가 비었는지 여부를 boolean으로 리턴한다.
  • clear() - 인덱스를 비운다.
  • indexOf(E x) - 배열 리스트 원소 x의 인덱스를 리턴한다. 중복 원소가 있을 경우 먼저 나오는 원소 인덱스를 리턴한다.
  • printAll() - 배열 리스트의 모든 원소를 리턴한다.

 

1) 노드 클래스

public class Node<E> {

	public E item;
	public Node<E> next;
	
	public Node (E newItem) {
		item = newItem;
		next = null;
	}
	
	public Node (E newItem, Node<E> nextNode) {
		item = newItem;
		next = nextNode;
	}
}
  • item 변수에 데이터를 저장한다.
  • next 변수에 다음 노드를 저장한다. next는 다음 노드의 위치를 가리키는 포인터가 된다.
  • 첫 번째 생성자는 데이터를 입력받아 데이터를 item에 저장하고, 포인터 값은 null로 저장한다.
  • 두 번째 생성자는 데이터와 다음 노드를 입력받아 데이터를 item에 저장하고, 포인터 값에 다음 노드를 저장한다.

2) 인터페이스

public interface ListInterface<E>{
	
	public void add(int i, E x); 
	
	public void append (E x); 
	
	public void remove (int i); 
	
	public void removeItem (E x); 
	
	public E get(int i); 
	
	public void set(int i, E x);
	
	public int len();
	
	public boolean isEmpty(); 
	
	public void clear(); 
	
	public int IndexOf (E x); 
    
	public void printAll ();
	
}

 

3) CircularLinkedList 클래스

- 필드 영역

	//필드 영역
	private Node<E> tail;
	private int size;
  • tail 변수에 tail로 설정할 노드를 저장한다.
  • size 변수에 원형 연결 리스트의 크기를 저장한다.

- 생성자 영역

	//생성자 영역
	public CircularLinkedList() {
		size = 0;
		tail = new Node(-1);
		tail.next=tail;
	}
  • 초기 원형 연결 리스트의 크기는 0이다.
  • 초기 tail의 저장 데이터는 -1이며, 다음 노드로 자기 자신을 참조한다.

-메서드 영역

<getNode 메서드 구현>

public Node<E> getNode (int i) {
		if (i>size||i<-1) throw new IllegalArgumentException();
		Node<E> currNode = tail.next; 
		for (int j=0; j<=i; j++) {
			currNode=currNode.next;
		}
		return currNode;
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우 ( -1일 경우 더미헤드를 반환)
  • 변수 currNode에 head(tail.next) 노드를 저장한다.
  • 입력받은 인덱스까지 for문으로 원형 연결 리스트를 순회한 뒤 currNode를 리턴한다.

<add 메서드 구현>

	@Override
	public void add(int i, E x) {
		if (i>size||i<0) throw new IllegalArgumentException();
		Node<E> prevNode = getNode(i-1);
		Node<E> currNode = new Node(x,prevNode.next);
		prevNode.next=currNode;
		
		if (i==size) {
			tail = currNode;
		}
		
		size++;
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • 변수 prevNode에 입력받은 인덱스 전 노드를 저장한다.
  • 변수 currNode에 item이 x고 next로 prevNode의 다음 노드를 참조하는 새 노드를 만들어 저장한다.
  • prevNode가 next로 currNode를 참조하게 한다.
  • 만약 원형 연결 리스트의 끝부분에 currNode를 저장하는 것이라면 tail을 currNode로 교체한다.
  • size 변수를 1 증가시킨다.

<append 메서드 구현>

	public void append(Object x) {
		Node<E> prevNode = tail;
		Node<E> newNode = new Node(x,prevNode.next);
		tail.next=newNode;
		tail = newNode;
		size++;
	}
  • 변수 prevNode에 tail 노드를 저장한다. 마지막 자리에 덧붙일 것이므로 현재의 tail이 새로운 노드의 이전 노드가 된다.
  • newNode에 item이 x고 tail.next(head)를 next로 참조하는 새 노드를 만든다. 
  • 기존 tail노드가 next로 newNode를 참조하게 한다.
  • newNode가 tail이 된다.
  • size 변수를 1 증가시킨다.

<remove 메서드 구현 - 단일 연결 리스트와 동일>

	@Override
	public void remove(int i) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		Node<E> prevNode = getNode(i-1);
		prevNode.next=getNode(i+1);
		size--;
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • 변수 prevNode에 입력받은 인덱스의 이전 인덱스 노드를 저장한다.
  • prevNode가 다음 노드로 i번째 노드를 생략하고 i+1번째 노드를 참조하게 한다.

<removeItem 메서드 구현>

	@Override
	public void removeItem(E x) {
		
		Node<E> currNode = tail.next;
		Node<E> prevNode;
		
		for (int i=0; i<size; i++) {
		
			prevNode = currNode;
			currNode = currNode.next;
			
			if(currNode.item.equals(x)) {
				prevNode.next=currNode.next;
				size--;
				return;
			}
			
		}
		
	}
  • for문으로 연결 리스트를 순회한다.
  • currNode의 데이터가 x와 일치하면 prevNode가 next값으로 currNode를 생략하고 currNode.next를 참조하게 한다.

<get, set, len, isEmpty 메서드 구현>

	@Override
	public E get(int i) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		return getNode(i).item;
	}
	
	@Override
	public void set(int i, E x) {
		if (i>size-1||i<0) throw new IllegalArgumentException();
		Node<E> currNode = getNode(i);
		currNode.item=x;
	}
	
	@Override
	public int len() {
		return size;
	}
	
	@Override
	public boolean isEmpty() {
		return (size==0);
	}
  • get - 입력받은 인덱스에 해당하는 값을 리턴한다.
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • set - 입력받은 인덱스에 해당하는 값을 입력받은 값으로 교체한다.
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • len - size 변수를 리턴한다.
  • isEmpty - size==0 여부를 리턴한다.

<clear 메서드 구현>

@Override
	public void clear() {
		size=0;
		tail=new Node(-1);
		tail.next=tail;
	}
  • size 변수를 0으로 초기화한다.
  • tail 변수를 item 값이 -1인 새 노드로 초기화한다.
  • tail 변수가 다음 노드로 자기 자신을 참조하게 한다. (tail.next==head)

<IndexOf 메서드 구현>

@Override
	public int IndexOf(E x) {
		int idx=-1;
		
		Node<E> currNode = tail.next;
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			
			if (currNode.item.equals(x)) {
				idx=i;
				break;
			}
		}
		return idx;
	}
  • for문으로 원형 연결 리스트를 순회하며 노드를 변수 currNode에 저장한다.
  • currNode.item과 입력받은 x를 비교한다.
  • currNode.item==x일때의 인덱스를 리턴한다.
  • 만약 x와 같은 값이 없으면 -1를 리턴한다.

<printAll 메서드 구현>

	@Override
	public void printAll() {
		
		Node<E> currNode = tail.next;
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			System.out.print(currNode.item+" ");
		}
		
		System.out.println();
		
	}
  • for문으로 원형 연결 리스트를 순회하며 노드를 변수 currNode에 저장한다.
  • 매 순회마다 currNode.item을 출력한다.

 

cf) 개인적으로 tail, tail.next, head의 구분이 어려웠어서 따로 정리하였다.

처음 CircularLinkedList를 생성하면 이 상태이다. tail 노드가 있고 이 노드는 next로 자기 자신을 참조한다. 개념상 tail.next가 head이므로 tail==tail.next==head가 된다.

값 100을 append하면 위와 같은 상태가 된다. newNode가 기존 tail.next(=tail)를 참조하고, 기존 tail이 newNode를 참조하며 tail 자리에 newNode가 들어간다. 

값 200을 append하면 위와 같은 상태가 된다. newNode가 기존 tail.next을 참조하고, 기존 tail(100)이 newNode를 참조하며 tail자리에 newNode가 들어간다.

** 기존 tail.next는 head이므로 newNode는 항상 head를 참조하는 것이 된다.

 

- 전체 코드

public class CircularLinkedList<E> implements ListInterface<E> {


		//필드 영역
		private Node<E> tail;
		private int size;
		
		//생성자 영역
		public CircularLinkedList() {
			size = 0;
			tail = new Node(-1);
			tail.next=tail;
		}
		//메서드 영역
		
		public Node<E> getNode (int i) {
			if (i>size-1||i<-1) throw new IllegalArgumentException();
			Node<E> currNode = tail.next; 
			for (int j=0; j<=i; j++) {
				currNode=currNode.next;
			}
			return currNode;
		}
		
		@Override
		public void add(int i, E x) {
			if (i>size||i<0) throw new IllegalArgumentException();
			Node<E> prevNode = getNode(i-1);
			Node<E> currNode = new Node(x,prevNode.next);
			prevNode.next=currNode;
			
			if (i==size) {
				tail = currNode;
			}
			
			size++;
		}
		
		@Override
		public void append(E x) {
			Node<E> prevNode = tail;
			Node<E> newNode = new Node(x,prevNode.next);
			tail.next=newNode;
			tail = newNode;
			size++;
			
		}
		
		@Override
		public void remove(int i) {
			if (i>size-1||i<0) throw new IllegalArgumentException();
			Node<E> prevNode = getNode(i-1);
			prevNode.next=getNode(i+1);
			size--;
		}
		
		@Override
		public void removeItem(E x) {
			
			Node<E> currNode = tail.next;
			Node<E> prevNode;
			
			for (int i=0; i<size; i++) {
			
				prevNode = currNode;
				currNode = currNode.next;
				
				if(currNode.item.equals(x)) {
					prevNode.next=currNode.next;
					size--;
					return;
				}
				
			}
			
		}
		
		@Override
		public E get(int i) {
			if (i>size-1||i<0) throw new IllegalArgumentException();
			return getNode(i).item;
		}
		
		@Override
		public void set(int i, E x) {
			if (i>size-1||i<0) throw new IllegalArgumentException();
			Node<E> currNode = getNode(i);
			currNode.item=x;
		}
		
		@Override
		public int len() {
			return size;
		}
		
		@Override
		public boolean isEmpty() {
			return (size==0);
		}
		
		@Override
		public void clear() {
			size=0;
			tail= new Node(-1);
			tail.next=tail;
		}
		
		@Override
		public int IndexOf(E x) {
			int idx=-1;
			
			Node<E> currNode = tail.next;
			
			for (int i=0; i<size; i++) {
				currNode=currNode.next;
				
				if (currNode.item.equals(x)) {
					idx=i;
					break;
				}
			}
			return idx;
		}
		
		@Override
		public void printAll() {
			
			Node<E> currNode = tail.next;
			
			for (int i=0; i<size; i++) {
				currNode=currNode.next;
				System.out.print(currNode.item+" ");
			}
			
			System.out.println();
			
		}
	}

 

3) 결과 확인하기

<코드>

public class Test {
	public static void main(String[] args) {

	CircularLinkedList<Integer> lst = new CircularLinkedList<>();
	
	lst.add(0, 100);
	
	lst.add(1, 200);
	
	lst.add(2, 300);
	
	lst.append(400);
	
	lst.printAll();
	
	//100,200,300,400을 차례로 입력하고 출력합니다.
	
	lst.remove(0);
	
	lst.printAll();
	
	lst.removeItem(400);
	
	lst.printAll();
	
	//0번째 인덱스의 값과 내용물이 400인 값을 삭제하고 출력합니다.
	
	lst.IndexOf(300);
	
	//300의 인덱스를 출력합니다.
	
	System.out.println(lst.isEmpty());
	System.out.println(lst.len());
	
	//lst가 비었는지 여부와 lst의 길이를 출력합니다.
	
	System.out.println(lst.get(0));
	
	//인덱스 0의 값을 출력합니다.
	
	lst.set(0, 1000);
	
	lst.printAll();
	
	//인덱스 0의 값을 1000으로 바꾸고 출력합니다.
	
	}
}

<결과>

정상적으로 출력된다.

1. LinkedList란?

단일 연결 리스트

연결 리스트(LinkedList)란 각 노드가 데이터(item)와 포인터(next)를 가지고 한 줄로 연결되어 있는 방식으로 데이터를 저장하는 자료 구조이다. 연결리스트의 대표적 종류에는 단일 연결 리스트, 양방향 연결 리스트, 원형 연결 리스트가 있다.

  • 선언시 크기가 고정되어 있지 않다.
  • 데이터 접근시 처음부터 노드를 순회해야 하므로 배열 리스트에 비해 속도가 느리다.
  • 데이터 추가/삭제시 전후 노드의 포인터만 수정하면 되므로 배열 리스트에 비해 속도가 빠르다.

2. LinkedList 구현하기

구현할 내용은 아래와 같다.

  • add (int i, E x) - i번째 인덱스에 원소 x를 입력한다.
  • append (E x) - 배열 리스트의 끝에 원소 x를 입력한다.
  • remove (int i) - i번째 원소를 삭제한다.
  • removeItem (E x) - 배열 리스트에서 원소 x를 삭제한다. 중복 원소가 있을 경우 먼저 나오는 원소를 삭제한다.
  • get (int i) - i번째 원소를 리턴한다.
  • set (int i, E x) - i번째 인덱스의 원소를 x로 교체한다.
  • len() - 배열 리스트의 원소 개수를 리턴한다.
  • isEmpty() - 배열 리스트가 비었는지 여부를 boolean으로 리턴한다.
  • clear() - 인덱스를 비운다.
  • indexOf(E x) - 배열 리스트 원소 x의 인덱스를 리턴한다. 중복 원소가 있을 경우 먼저 나오는 원소 인덱스를 리턴한다.
  • printAll() - 배열 리스트의 모든 원소를 리턴한다.

1) 노드 클래스

public class Node<E> {

	public E item;
	public Node<E> next;
	
	public Node (E newItem) {
		item = newItem;
		next = null;
	}
	
	public Node (E newItem, Node<E> nextNode) {
		item = newItem;
		next = nextNode;
	}
}
  • item 변수에 데이터를 저장한다.
  • next 변수에 다음 노드를 저장한다. next는 다음 노드의 위치를 가리키는 포인터가 된다.
  • 첫 번째 생성자는 데이터를 입력받아 데이터를 item에 저장하고, 포인터 값은 null로 저장한다.
  • 두 번째 생성자는 데이터와 다음 노드를 입력받아 데이터를 item에 저장하고, 포인터 값에 다음 노드를 저장한다.

2) 인터페이스

public interface ListInterface<E>{
	
	public void add(int i, E x); 
	
	public void append (E x); 
	
	public void remove (int i); 
	
	public void removeItem (E x);
	
	public E get(int i); 
	
	public void set(int i, E x); 
	
	public int len(); 
	
	public boolean isEmpty(); 
	
	public void clear();
	
	public int IndexOf (E x); 
    
	public void printAll ();
	
}

3) LinkedList 클래스

- 필드 영역

//필드 영역
	private Node<E> head;
	private int size;
  • head 변수에는 연결 리스트의 시작 노드를 저장한다.
  • size에는 연결 리스트의 길이를 저장한다.

- 생성자 영역

	//생성자 영역
	public LinkedList() {
		size=0;
		head = new Node<>(null,null);
	}
  • 새 연결 리스트를 생성하면 필드 영역의 head에 데이터와 포인터값이 모두 null인 새 노드를 저장한다.
  • 연결 리스트의 크기인 size는 0이다.

- 메서드 영역

<getNode 메서드 구현>

public Node<E> getNode (int i) {
		if (i<-1||i>size) throw new IllegalArgumentException();
		Node<E> currNode = head;
		for (int j=0; j<=i; j++) {
			currNode=currNode.next;
		}
		return currNode;
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우 (-1인 경우 더미 헤드를 반환)
  • 인터페이스의 메서드를 오버라이딩 한 것은 아니다.
  • head 노드를 currNode 변수에 저장한다.
  • 입력받은 인덱스에 도달할 때까지 head부터 i만큼 포인터를 통해 다음 노드로 이동한 뒤 currNode를 리턴한다.

<add 메서드 구현>

	@Override
	public void add(int i, E x) {
		if (i<0||i>size) throw new IllegalArgumentException();
		Node<E> prevNode = getNode(i-1);
		Node<E> newNode = new Node<> (x,prevNode.next);
		prevNode.next=newNode;
		size++;
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • x를 item으로 하고 prevNode.next를 next로하는 newNode를 생성한다.
  • prevNode가 next로 newNode를 참조하게 한다.
  • size 변수를 1 증가시킨다.

<append 메서드 구현>

	@Override
	public void append(E x) {
		Node<E> prevNode = getNode(size-1); 
		Node<E> newNode = new Node<>(x,null);
		prevNode.next=newNode;
		size++;
  • prevNode에 연결 리스트의 마지막 인덱스 노드를 저장한다.
  • item에 입력 받은 데이터를, next에 null값을 입력한 newNode 객체를 생성한다.
  • prevNode가 next로 newNode를 참조하게 한다.

<remove 메서드 구현>

	@Override
	public void remove(int i) {
		if (i<0||i>size-1) throw new IllegalArgumentException();
		Node<E> prevNode = getNode(i-1);
		prevNode.next=getNode(i+1);
		size--;
	}
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • prevNode가 next로 prevNode.next를 생략하고 prevNode.next.next를 참조하게 한다.

<removeItem 메서드 구현>

	@Override
	public void removeItem(E x) {
		Node<E> currNode = head;
		Node<E> prevNode;
		
		for (int i=0; i<size; i++) {
			prevNode=currNode;
			currNode=prevNode.next;
			
			if (currNode.item.equals(x)) {
				prevNode.next=currNode.next;
				size--;
				return;
			}
		}
		
	}
  • for문으로 연결 리스트를 순회한다.
  • currNode의 데이터가 x와 일치하면 prevNode가 next값으로 currNode를 생략하고 currNode.next를 참조하게 한다.

<get,set,len,isEmpty 메서드 구현>

	@Override
	public E get(int i) {
		if (i<0||i>size-1) throw new IllegalArgumentException();
		Node<E> currNode = getNode(i);
		return currNode.item;
	}

	@Override
	public void set(int i, E x) {
		if (i<0||i>size-1) throw new IllegalArgumentException();
		Node<E> currNode = getNode(i);
		currNode.item=x;
	}

	@Override
	public int len() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return (size==0);
	}
  • get - 입력받은 인덱스에 해당하는 값을 리턴한다.
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • set - 입력받은 인덱스에 해당하는 값을 입력받은 값으로 교체한다.
  • 예외처리 - 입력받은 인덱스가 범위를 벗어나는 경우
  • len - size 변수를 리턴한다.
  • isEmpty - size==0 여부를 리턴한다.

<clear 메서드 구현>

	@Override
	public void clear() {
		size = 0;
		head = new Node<>(null,null);
	}
  • size 변수를 0으로 초기화한다.
  • head 변수를 새로운 노드로 초기화한다.

<indexOf 메서드 구현>

	@Override
	public int IndexOf(E x) {
		
		int idx=-1;
		Node<E> currNode = head;
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			
			if (currNode.item.equals(x)) {
				idx=i;
				break;
			}
		}
		
		return idx;
	}
  • for문으로 연결 리스트를 순회하며 노드를 변수 currNode에 저장한다.
  • currNode.item과 입력받은 x를 비교한다.
  • currNode.item==x일때의 인덱스를 리턴한다.
  • 만약 x와 같은 값이 없으면 -1를 리턴한다.

<printAll 메서드 구현>

	@Override
	public void printAll() {
		
		Node<E> currNode = head;
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			System.out.print(currNode.item+" ");
		}
		
		System.out.println();
		
	}
  • for문으로 연결 리스트를 순회하며 노드를 변수 currNode에 저장한다.
  • 매 순회마다 currNode.item을 출력한다.

- 전체 코드

public class LinkedList<E> implements ListInterface<E> {
	
	//필드 영역
	private Node<E> head;
	private int size;
	
	//생성자 영역
	public LinkedList() {
		size=0;
		head = new Node<>(null,null);
	}
	
	//메서드 영역
	
	public Node<E> getNode (int i) {
		if (i<-1||i>size) throw new IllegalArgumentException();
		Node<E> currNode = head;
		for (int j=0; j<=i; j++) {
			currNode=currNode.next;
		}
		return currNode;
	}
	
	@Override
	public void add(int i, E x) {
		if (i<0||i>size) throw new IllegalArgumentException();
		Node<E> prevNode = getNode(i-1);
		Node<E> newNode = new Node<> (x,prevNode.next);
		prevNode.next=newNode;
		size++;
	}

	@Override
	public void append(E x) {
		Node<E> prevNode = getNode(size-1); //while문으로 prevNode.next==null일때까지 순회해서 찾는 것도 가능
		Node<E> newNode = new Node<>(x,null);
		prevNode.next=newNode;
		size++;
		
	}

	@Override
	public void remove(int i) {
		if (i<0||i>size-1) throw new IllegalArgumentException();
		Node<E> prevNode = getNode(i-1);
		prevNode.next=getNode(i+1);
		size--;
	}

	@Override
	public void removeItem(E x) {
		Node<E> currNode = head;
		Node<E> prevNode;
		
		for (int i=0; i<size; i++) {
			prevNode=currNode;
			currNode=prevNode.next;
			
			if (currNode.item.equals(x)) {
				prevNode.next=currNode.next;
				size--;
				return;
			}
		}
		
	}

	@Override
	public E get(int i) {
		if (i<0||i>size-1) throw new IllegalArgumentException();
		Node<E> currNode = getNode(i);
		return currNode.item;
	}

	@Override
	public void set(int i, E x) {
		if (i<0||i>size-1) throw new IllegalArgumentException();
		Node<E> currNode = getNode(i);
		currNode.item=x;
	}

	@Override
	public int len() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return (size==0);
	}

	@Override
	public void clear() {
		
		size = 0;
		head = new Node<>(null,null);
		
	}

	@Override
	public int IndexOf(E x) {
		
		int idx=-1;
		Node<E> currNode = head;
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			
			if (currNode.item.equals(x)) {
				idx=i;
				break;
			}
		}
		
		return idx;
	}

	@Override
	public void printAll() {
		
		Node<E> currNode = head;
		
		for (int i=0; i<size; i++) {
			currNode=currNode.next;
			System.out.print(currNode.item+" ");
		}
		
		System.out.println();
		
	}

}

3) 결과 확인하기

<코드>

public class Test {
	public static void main(String[] args) {

	LinkedList<Integer> lst = new LinkedList<>();
	
	lst.add(0, 100);
	
	lst.add(1, 200);
	
	lst.add(2, 300);
	
	lst.append(400);
	
	lst.printAll();
	
	//100,200,300,400을 차례로 입력하고 출력합니다.
	
	lst.remove(0);
	
	lst.printAll();
	
	lst.removeItem(400);
	
	lst.printAll();
	
	//0번째 인덱스의 값과 내용물이 400인 값을 삭제하고 출력합니다.
	
	lst.IndexOf(300);
	
	//300의 인덱스를 출력합니다.
	
	System.out.println(lst.isEmpty());
	System.out.println(lst.len());
	
	//lst가 비었는지 여부와 lst의 길이를 출력합니다.
	
	System.out.println(lst.get(0));
	
	//인덱스 0의 값을 출력합니다.
	
	lst.set(0, 1000);
	
	lst.printAll();
	
	//인덱스 0의 값을 1000으로 바꾸고 출력합니다.
	
	}
}

<결과>

정상적으로 출력된다.

+ Recent posts