












Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Community
Ask the community for help and clear up your study doubts
Discover the best universities in your country according to Docsity users
Free resources
Download our free guides on studying techniques, anxiety management strategies, and thesis advice from Docsity tutors
This document compares the implementation of stacks and queues using linked lists and arrays. It discusses the advantages and disadvantages of each approach, including space efficiency, ease of implementation, and access time. The document also provides code examples for both array-based and linked list-based stacks and queues.
Typology: Lecture notes
1 / 20
This page cannot be seen from the preview
Don't miss anything!
연결구조 연결구조를 이용한 스택의 구현 연결구조를 이용한 큐의 구현 연결구조를 이용한 순환 큐의 구현 연결구조를 이용한 정렬 리스트의 구현 연결구조를 이용한 비정렬 리스트 구현
연결구조연결구조란 고정된 공간을 미리 확보하여 사용하지 않고 , 필요할필요할 때마다때마다 동적으로동적으로 확보하여확보하여 사용하는사용하는 구조구조를 말한다. 배열을 이용한 구현 방식에서는 요소를 저장할 공간을 미리 확보하며 , 이 공간이 고정되어 있다. 따라서 공간이 낭비되거나 부족할 수 있다. ArrayList 처럼 부족하면 보다 큰 공간을 확보하여 새 공간에 기존 요소를 복사하여 사용하는 방식을 취할 수 있다. 이 방식은 공간이 부족할 때 많은 비용이 소요된다는 문제점이 있다. 연결구조는 하나의 요소를 저장할 때마다 공간을 확보한다. 이렇게 확보한 공간을 연결하지 않으면 저장한 요소들을 접근하기 어렵기 때문에 배열과배열과 달리달리 하나의하나의 요소를요소를 저장할저장할 때마다때마다 추가추가 공간이공간이 필요필요하다. 이것이 연결구조의 한 가지 단점이다. info info info info info info info info 배열방식 (^) 연결 구조 방식
도 공간 부족 문제를 극복하기 위해 ArrayList처럼 부족할 때마다 보다 큰 공간을 확보하여
정렬 구조일 경우에는 후속 중간에 요소 삽입 연결만 변경 (상수 시간) 요소들을 모두 이동해야 함.
자료구조를 처음 생성할 때 한번 확보
새 요소를 삽입할 때마다 공간 확보 비용 확보해야 함 임의 정렬 구조의 경우에는 이진 검색 가능
순차 정렬 구조의 경우에도 이진 검색을 할 수 없음
각 요소 접근 방식
한 요소가 차지하는 공간 요소+연결 요소
공간부족/낭비 문제 없음 있음
연결구조 배열을 이용한 구현 방식
연결구조
연결구조로 스택의 구현
info link info link A B
info null C
private class StackNode{ public Object info;public StackNode link; }
LinkedStack stack = new LinkedStack(); stack top null stack.push(new Integer(10)); stack top info null 10
stack.push(new Integer(20)); stack top
info null 10
info 20
연결구조를 이용한 스택의 구현 StackNode top = null; 연결 구조이므로 배열과 달리 색인이 아닌 참조 타입으로 스택 top 을 유지함 생성자 LinkedStack() 상태 boolean isFull(); 조건 : 항상 거짓 boolean isEmpty(); 조건 : top==null
조작 void push(Object item); StackOverflowException 이 발생하지 않음 void pop(); isEmpty() 가 참이면 StackUnderflowException 발생 Object top(); isEmpty() 가 참이면 StackUnderflowException 발생 stack top null
따라서 연결구조에서 isFull 메소드는 보통 항상 false를 반환하도록 정의하며, 새 요소를 삽 입하는 연산은 OverflowException의 발생을 고려하지 않는다.
public class LinkedStack implements Stack{ private class StackNode{ public Object item; public StackNode link; } private StackNode top = null; public StackNode() {} … }
클래스 내부에 정의한 클래스를 내부 클래스 (inner class) 라 한다.
privateclass
class
publicclass
public class
패키지
private 내부 클래스는 그것의 외부 클래스를 제외하고는 접근할 수 없으므로 내부 클래스의 멤버 변수를 public 으로 선언하여도 안전하다.
바에서 private 내부 클래스는 다른 패키지에 있는 클래스는 물론 같은 패기지 내에 있는 다 른 클래스들로부터 숨길 수 있다. 따라서 연결구조의 노드를 나타내는 클래스는 내부 클래 스로 정의하여 사용하는 것이 바람직하다. 같은 이유에서 private 내부 클래스의 멤버들은 public으로 지정하여도 안전하다.
내부 클래스를 사용하는 목적 내부 클래스는 그것을 생성한 외부 클래스 객체의 구현 ( 멤버 변수 , 메소드 ) 을 접근 권한과 무관하게 모두 접근할 수 있다. 같은 패키지에 있는 다른 클래스로부터 숨길 수 있다. (name control 측면 ) private 내부 클래스를 사용하여 사건 기반 프로그램을 작성할 때 매우 유용하다. top level class inner class
당연한 특성
외부 클래스 객체의 구현을 접근할 수 있다. static inner class 인 경우에는 가능하지 않다.
연결연결 구조에서는구조에서는 이이 측면측면 때문에때문에 사용사용
서 newNode.link = top 문장과 top = newNode 문장의 순서가 바뀌면 기존 요소들과의 연 결이 끊어지게 된다.
pop 알고리즘 단계단계^ 1.1.^ 스택이 비어 있는지 확인함 단계단계 2.2. top 이 top.link 을 가리키도록 함
stack top
info null 10
info 20
1
garbage
public void pop() throws StackUnderflowException{ if(isEmpty()) throw new StackUnderflowException(“…”); top = top.link; }
연결구조의 첫 요소가 스택의 톱이 되므로 pop 연산은 첫 요소를 제거하면 된다. 즉, 첫 요 소를 스택 톱으로 사용해야 push와 pop 연산을 모두 효율적으로 처리할 수 있다.
stack top
info null 10
info
20
public Object top() throws StackUnderflowException{ if(isEmpty()) throw new StackUnderflowException(“…”); return top.info; }
top 연산은 스택의 상태를 변경하지 않고 스택의 톱에 있는 요소를 반환하면 된다.
Which one is better? 상황에 따라 다르다. 공간 활용 측면에서 LinkedStack 이 우수 스택의 크기가 매우 가변적인 경우 LinkedStack 이 우수 ArrayStack 은 공간 낭비 가능 push 가 빈번하게 일어나는 경우 : ArrayStack 우수 LinkedStack 은 매번 새 공간을 확보해야함
constructor O(N) O(1) space N+1 참조 , 1 색인 2N+1 참조 (no waste)
pop O(1) O(1)
push O(1) O(1)
ArrayStack LinkedStack
수 있듯이 두 구현 모두 push와 pop은 상수 시간 알고리즘이다. 하지만 배열은 공간을 초 기에 모두 확보하는 반면 스택은 새 요소를 추가할 때마다 확보한다. 공간적인 측면에서는 배열에 N개의 요소가 저장되어 있다면 N+1 참조와 스택의 톱 정보를 나타내는 색인 정보 가 필요하다. 하지만 배열은 초기에 공간을 확보하고, 이 공간이 고정되어 있으므로 낭비가 되는 공간이 있을 수 있다. 반대로 연결구조를 이용한 구현에서 N개의 요소가 저장되어 있 다면 2N개의 공간이 필요하며, 스택의 톱 정보를 유지하는 참조 타입의 변수가 추가로 필 요하다. 하지만 이 공간들 중에 낭비가 되는 공간은 없다. 이런 측면에서 보면 어느 구현 방식이 절대적으로 우수하다고 말할 수 없다. 상황에 따라 구현방식을 결정해야 한다.
연결 구조를 이용한 큐의 구현 QueueNode front = null; QueueNode rear = null; int size = 0; 생성자 LinkedQueue() 상태 boolean isFull(); 조건 : 항상 거짓 boolean isEmpty(); 조건 : front==null
조작 void enq(Object item); QueueOverflowExceptio 은 발생하지 않음 Object deq(); isEmpty() 가 참이면 QueueUnderflowException 발생
연결구조 방식의 구현이므로 스택과 마찬기지로 isFull 메소드는 항상 false를 반환하도록 구 현한다. isEmpty는 큐에 첫 노드를 가리키는 front을 이용하여 검사할 수도 있고, front을 이 용하지 않고, size를 이용하여 검사할 수도 있다.
enqueue 알고리즘 단계단계 1.1. 새 요소를 위한 노드 생성 단계단계^ 2.2.^ 이 요소의^ info^ 값에^ item^ 할당 단계단계 3.3. 이 요소를 rear 에 추가 단계 4. rear 가 이 요소를 가리키도록 변경함
info 10
queue front rear
info null 20
1. 생성 2
3
public void enq(Object item) { QueueNode newNode = new QueueNode(); newNode.info = item; newNode.link = null; // 큐가 비어 있는 경우 if(rear == null) front = newNode; // 큐에 요소가 있는 경우 else rear.link = newNode; rear = newNode; size++; }
size 2
enqueue는 큐의 맨 끝에 추가하는 것이므로 보통 연결구조의 맨 끝 노드를 가리키고 있는 rear 정보만 변경하면 된다. 하지만 현재 큐가 비어 있는 경우에는 맨 앞 노드를 가리키고 있는 front 정보도 변경되어야 한다. 위 슬라이드의 코드를 보면 rear를 이용하여 큐가 비어 있는지 검사하고 있다. 이 부분은 size를 이용하여 검사할 수도 있고, isEmpty 메소드를 호 출하여 검사할 수도 있다. 물론 메소드의 호출보다는 멤버 변수를 검사하여 판단하는 것이 효율적이다.
public Object deq() throws QueueUnderflowException{if(isEmpty()) throw new Object tmp = front.info;QueueUnderflowException(“…”); front = front.link; size--;if(isEmpty()) rear = null; return tmp; }
dequeue 알고리즘 단계단계 1.1. queue 가 비어있는지 검토 단계단계^ 2.2. front.item 을 임시 보관 단계단계 3.3. front 가 front.link 를 가리키도록 변경함 단계단계^ 4.4.^ 큐에 더 이상 요소가 없으면^ rear 를^ null 로 설정함 단계단계 5.5. 임시 보관한 item 을 반환
info 10
queue
info null 20 1
tmp 2
front rear size 1
dequeue는 enqueue와 반대로 보통 큐의 맨 앞 노드를 가리키고 있는 front 정보만 변경하 면 된다. 하지만 dequeue 결과 큐가 빈 상태가 되면 맨 끝 노드를 가리키고 있는 rear 정보 도 변경되어야 한다. 이처럼 연결구조를 구현할 때에는 먼저 가능한 모든 경우를 생각해보 고, 각 경우가 동일한 행동을 통해 해결 가능한지 검토해야 한다.
순환 연결 구조를 이용하면 하나의 참조만을 이용하여 큐를 구현할 수 있다. front 만 유지하면 dequeue 는 O(1) 이지만 enqueue 는 O(n) 이 된다. rear 만 유지하면 큐 맨 뒤는 rear 로 큐 맨 앞은 rear.link 로 바로 접근할 수 있다.
info 10
queue rear info 20
info 30
List
ArrayList LinkedList (^) use ListNode innerclass
UnsortedArrayList SortedArrayList UnsortedLinkedList SortedLinkedList
import java.util.Iterator;public interface List extends Iterable{ boolean isEmpty();boolean isFull(); void clear();int size(); boolean search(Object item);void insert(Object item); boolean delete(Object item);Object retrieve(Object item); } // ListIterator iterator();
리스트와 관련된 전체 클래스의 계층구조는 위 슬라이드와 같다. 여기서 List는 인터페이스 로서 모든 리스트 자료구조가 기본적으로 제공해야 하는 연산들을 선언하고 있다. java,lang 패키지에는 Iterable 인터페이스가 정의되어 있으며, 이 인터페이스를 구현하는 클래스는 Iterator iterator(); 형태의 반복자를 제공해야 한다. Iterator는 java.util 패키지에 정의되어 있 는 인터페이스로서, 반복자 클래스가 기본적으로 제공해야 하는 연산들을 선언하고 있다. ArrayList와 LinkedList는 각각 배열을 이용한 구현, 연결구조를 이용한 구현을 위한 추상 클 래스이다.
Import java.util.Iterator; public abstract class LinkedList implements List{protected class ListNode{ public Object info; } // ListNodepublic ListNode next; protected class ListIterator implements Iterator{ } // ListIterator… protected ListNode list = null; protected int size = 0; public LinkedList(){}public boolean isFull(){ return false; } public boolean isEmpty(){ return (list == null); } void clear(){ … }public int size(){ return size; } public Iterator iterator() { return new ListIterator(list); } public abstract boolean search(Object item);public abstract void insert(Object item); public abstract boolean delete(Object item); public abstract Object retrieve(Object item); } // LinkedList
ListNode protected 와인 이유는 자식 클래스인 ListIterator 내부 클래스가 UnsortedLinkedList 와 SortedLinkedList 에서 활용하기 위함이다.
반복자 클래스는 LinkedList의 내부 클래스로 정의하였다. 이것은 외부로부터 이 클래스를 숨기기 위한 목적보다는 내부 클래스는 그 클래스가 속한 외부 클래스의 멤버를 쉽게 접근 할 수 있기 때문에 이 기능을 활용하기 위함이다. 하지만 반드시 이와 같이 반복자 클래스 를 내부 클래스로 구현할 필요는 없다. 반복자 클래스 ListIterator와 노드를 나타내는 ListNode 클래스를 protected로 선언한 이유는 자식 클래스들인 UnsortedLinkedList와 SortedLinkedList에서 이들에 대한 접근을 용이하게 하기 위함이다.
protected class ListIterator implements Iterator{public ListNode cursor; public int traverseCount = 0; public ListIterator(ListNode node){cursor = node; } public Object next(){Object tmp = cursor.info; cursor = cursor.next; traverseCount++; } return tmp; public boolean hasNext(){ return (traverseCount<size);// return (cursor!=null) } public void remove(){throw new UnsupportedOperationException(); } } // ListIterator
반드시 반복자 클래스를 내부 클래스로 구현할 필요는 없다.
traverseCount 변수를 사용하지 않고 cursor 하지만 만을 이용하여 구현할 수도 있다 CircularLinkedList 까지 고려하여. traverseCount 변수를 활용하고 있다.
java.util 패키지에 정의되어 있는 Iterator 인터페이스에는 next, hasNext, remove 세 가지 메 소드가 선언되어 있다. 이 중 remove는 현재 방문 중인 요소를 삭제할 때 사용하는 메소드 이다. 이 메소드를 제공할 필요가 없는 경우에는 UnsupportedOperationException 예외를 발 생하도록 구현하면 된다. LinkedList에서 사용하는 반복자 클래스는 traverseCount라는 멤버 변수를 사용하고 있는데, 이 멤버 변수를 사용하지 않고 hasNext() 메소드에서 cursor!=null 을 이용하여 구현할 수 있다. 하지만 여기서 traverseCount를 사용하는 이유는 다음 장에서 살펴볼 순환 연결 리스트에서도 같은 반복자 클래스를 사용하기 위함이다. 생성자에서 cursor를 list로 초기화하지 않고 node라는 매개변수를 받아 cursor를 초기화하는 이유도 같 은 이유이다.
public boolean search(Object item) throws ListUnderflowException{ if(isEmpty()) throw new ListUnderflowException(“…”); if(item==null) throw new NullPointerException(“…”);ListNode loc = list; for(int i=0; i<size; i++){ if(item.equals(loc.info)) return true; } // forelse loc = loc.next; return false; } // UnsortedList: search
info 10
info 20
info 30
info 50
list
public boolean search(Object item) throws ListUnderflowException{ if(isEmpty()) throw new ListUnderflowException(“…”);if(item==null) throw new NullPointerException(“…”); Comparable x = (Comparable)item; ListNode loc = list; boolean moreToSearch = true;do{ int compResult = x.compareTo(loc.info); if(compResult==0) return true;else if(compResult<0) return false; else{ loc = loc.next; moreToSearch = (loc != null); } }while(moreToSearch);return false; } // SortedLinkedList: search ( 중간에 중단 가능 )
검색을 중간에서 중단할 수도 있다. 중간에 중단하기 위해서는 equals 메소드 대신에 compareTo 메소드를 사용해야 한다. 또한 이를 위해 인자로 받은 item을 Comparable 타입 으로 타입변환해야 한다.
public boolean search(Object item) throws ListUnderflowException{ if(item==null) throw new NullPointerException(“…”); if(isEmpty()) throw new ListUnderflowException(“…”);Comparable x = (Comparable)item; ListNode loc = list; for(int i=0; i<size; i++){int compResult = x.compareTo(loc.info); if(compResult==0) return true; else if(compResult<0) return false;else loc = loc.next; } // for return false; } // SortedLinkedList: search ( 중간에 중단 가능 )
info 10
info 50
info 30
info 10
list prevLoc loc
public boolean delete(Object item) throws ListUnderflowException{ if(item==null) throw new NullPointerException(“…”);if(isEmpty()) throw new ListUnderflowException(“…”); ListNode loc = list; ListNode prevLoc = null; for(int i=0; i<size; i++){if(x.equals(loc.info)){ // 삭제할 요소가 있는 경우 if(prevLoc==null) list = list.next; // 리스트의 처음 else prevLoc.next = loc.next; size--;return true; } else{ // prevLoc = loc; loc = loc.next; 검색을 계속해야 하는 경우 } } // forreturn false; } // UnsortedLinkedList: delete
노드로 변경해주어야 한다. 이를 위해 삭제하고자 하는 요소를 찾을 때 현재 노드 정보(loc) 와 현재 노드의 선행 노드 정보(prevLoc)를 함께 유지해야 한다. 또 리스트을 조작할 때에는 크게 네 가지 경우로 나누어 고려해야 한다. 첫째, 리스트가 비어 있는 경우 둘째, 리스트 맨 앞에 요소를 추가/맨 앞에 있는 요소를 삭제하는 경우 셋째, 리스트 중간에 요소를 추가/중간에 있는 요소를 삭제하는 경우 넷째, 리스트 맨 끝에 요소를 추가/맨 끝에 있는 요소를 삭제하는 경우 삭제의 경우에는 리스트가 비어 있는 경우에는 ListUnderflowException을 발생한다. 맨 앞에 있는 요소를 삭제하는 경우에는 리스트의 첫 노드를 가리키는 list 정보도 변경해야 한다. 중 간 또는 끝에 있는 요소를 삭제하는 경우는 모두 현재 노드의 선행 노드의 연결을 현재 노 드의 후속 노드를 가리키도록 변경해주면 된다. 즉, 중간 또는 끝에 있는 요소의 삭제는 동
public void insert(Object item){ if(item==null) throw new NullPointerException(“…”);Comparable x = (Comparable)item; ListNode newNode = new ListNode(); newNode.info = item;ListNode loc = list; ListNode prevLoc = null; for(int i=0; i<size; i++){ // 삽입할 위치 결정 if(x.compareTo(loc.info)<0) break;else{ prevLoc = loc; } loc = loc.next; } // for if(prevLoc==null){ // newNode.next = list; 리스트의 맨 처음 list = newNode; } else{ // newNode.next = loc; 리스트 중간 또는 끝 prevLoc.next = newNode; }size++; } // SortedLinkedList: insert
info 20
info 30
info 2 25 1
info 10
list 1
void print(SortedLinkedList l){ Iterator i = l.iterator(); while(i.hasNext()){System.out.print(i.next()+”,”); } System.out.println(); }
내부 클래스로 구현된 반복자 클래스를 사용하는 방법은 위 슬라이드와 같다. 즉, iterator 메소드를 호출하여 반복자를 생성한 다음에 hasNext와 next 메소드를 이용하여 구조에 저 장되어 있는 요소들을 차례로 방문할 수 있다.
O(N), O(1) 전체 : O(N)
O(logN), O(N) 전체 : O(N)
O(N), O(1) 전체 : O(N)
O(N), O(1) delete 전체 : O(N) space N+1 참조 , 2 정수 2N+2 참조 , 1 정수 N+1 참조 , 2 정수 2N+2 참조 , 1 정수
O(N), O(1) 전체 : O(N) O(logN), O(N) insert O(1) O(1) 전체 : O(N)
retrieve O(N) O(N) O(logN) O(N)
search O(N) O(N) O(logN) O(N)
Unsorted, Array Unsorted, Linked Sorted, Array Sorted, Linked