






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 chapter explores the implementation methods of stack and queue data structures using arrays. The educational objectives of this chapter are to understand the characteristics of stacks and queues, and to learn how to implement them using arrays. The stack is a last-in-first-out (lifo) data structure where the most recent element is at the top, and the oldest element is at the bottom. The queue is a first-in-first-out (fifo) data structure where the most recent element is added to the rear, and the oldest element is removed from the front. This chapter covers the implementation of both stack and queue using arrays, including exception handling.
Typology: Lecture notes
1 / 11
This page cannot be seen from the preview
Don't miss anything!
이 장에서는 배열을 이용한 스택(stack)과 큐(queue) 자료구조의 구현 방법을 살펴본다.
스택과 큐의 특성을 살펴보고 , 배열을 이용한 스택과 큐의 구현방법을 학습한다.
스택의 특성 순서가순서가 있는있는 동질동질 구조구조 가장 최근에 추가된 요소가 맨 위 (top) 에 있고 , 가장 오래 전에 추가된 요소가 맨 밑 (bottom) 에 있다. LIFO(LastLIFO(Last--InIn--FirstFirst--Out)Out) 구조구조 : 요소의 제거 또는 추가는 스택의 top 에서만 이루어진다. 연산 push: 요소를 추가하는 연산 pop: 요소를 스택에서 제거하고 , 맨 위 요소를 반환해주는 연산 top: 스택의 변화 없이 맨 위 요소를 반환해주는 연산
한다. 이 때 요소가 추가되고, 삭제되는 위치를 스택의 톱(top)이라 한다. 이런 방식으로 추
가와 삭제가 이루어지기 때문에 스택을 LIFO(Last-In-First-Out) 구조라 한다. 즉, 가장 나중 에 추가된 요소가 가장 먼저 추출되는 구조이다. 스택은 보통 크게 push, pop, top 세 가지 연산을 제공한다. push는 요소를 스택에 추가할 때 사용하는 연산이고, pop은 스택 톱에 있 는 요소를 제거할 때 사용하는 연산이며, top은 스택 톱에 있는 요소를 열람할 때 사용하는 연산이다. push와 pop은 스택의 상태를 변화시키지만 top은 스택의 상태를 그대로 유지한 다.
예외 상황 push: 배열을 이용하여 구현할 경우에는 더 이상 추가할 수 없는 상황이 발생할 수 있다. pop, top: 스택에 요소가 하나도 없을 수 있다.
stack =new Stack() stack.push(2) stack.push(3) stack.top() stack.pop()
2 2
3 3 2
모든 항에 요소가 있어 더 이상 새 요소를 추가할 수 없는 상황에 push 연산을 시도할 수 있다. 둘째, pop, top 연산은 스택에 아무런 요소도 없을 때 pop 또는 top 연산을 시도할 수 있다.
StackOverflowException: 스택이 꽉 차있을 때 push 를 시도하면 발생
StackUnderflowException: 스택이 비어 있을 때 pop 또는 top 을 시도하면 발생
public class StackOverflowException extends RuntimeException{public StackOverflowException(){ super(“ 스택이 꽈 차 있는 상태에서 push 를 시도하였음. ”); } } public StackOverflowException(String msg){ super(msg); }
public class StackUnderflowException extends RuntimeException{ public StackUnderflowException(){super(“ 스택이 비어 있는 상태에서 pop/top 을 시도하였음. ”); } public StackUnderflowException(String msg){ super(msg); } }
이를 위해 StackOverflowException과 StackUnderflowException을 정의하여 사용한다. 참고적
드는 역시 topindex 정보를 통해 판별할 수 있다. Listable 인터페이스를 활용하여 복사 방식 으로 구현하더라도 이 슬라이드처럼 입력 타입, 출력 타입, 내부 배열 타입은 계속 Object를 사용할 수 있다. push 메소드의 인자 타입을 Listable로 사용하는 것과 Object로 사용하는 것의 차이점은 다음과 같다. Listable 타입을 사용하면 이 클래스를 사용하는 측에서는 이 사실을 알고 보통 Listable 타입의 객체를 전달할 것이다. 하지만 Object 타입으로 되어 있 으면 Listable 타입이 요구되는지 몰라 Listable 타입이 아닌 다른 타입의 객체를 전달할 수 도 있다. 이 측면에서 보면 인자 타입을 Listable로 사용하는 것이 보다 효과적인 방법이지 만 이번 장을 제외하고는 복사 방식으로 구현하지 않기 때문에 인자 타입이나 출력 타입으 로 Object 타입을 사용한다.
public void push(Object item) throws StackOverflowException{ if(isFull()) throw new StackOverflowException(“Push attempted on a full stack.”); Listable x = (Listable)item; topindex++; elements[topindex] = x.clone(); }
2
topindex = - 2
topindex = 0
push(2) 2
2 3
topindex = 1
push(3) 3 2
2 3 5
topindex = 2
push(5) 3
5
배열을 이용한 스택을 구현할 때 0 번째 색인을 스택의 톱으로 유지하면 topindex 정보 자체 를 유지할 필요가 없다. 하지만 매번 추가 또는 삭제할 때마다 모든 요소를 하나씩 이동해 야 한다. 따라서 이런 이동 없이 추가하고 삭제하기 위해서는 비정렬 리스트에서 요소를 추 가하듯이 맨 끝에 추가해야 한다. 따라서 매번 추가할 때마다 topindex 정보를 하나 증가시 킨 후에 그 위치에 추가한다.
2
2 3 5 1
topindex = 3
push(1) 3
5
1
2
2 3 5 1 7
topindex = 4
push(7) 3
5
1
7
2
2 3 5 1
topindex = 3
3
5
1 push(8) 실패
pop()
public void pop() throws StackUnderflowException{ if(isEmpty()) throw new StackUnderflowException(“Pop attempted on an empty stack”); elements[topindex] = null; // 불필요 topindex--; } public Object top() throws StackUnderflowException{ if(isEmpty()) throw new StackUnderflowException(“Top attempted on an empty stack”); Listable x = (Listable)elements[topindex];return x.clone(); }
pop 연산은 topindex를 하나 감소만 시키면 된다. 이 때 elements[topindex] = null 문장은 반드시 할 필요는 없다. 하지만 이것은 자바의 garbage collection을 촉진시킬 수 있기 때문 에 사용자의 프로그래밍 스타일에 따라 이렇게 하는 것을 권장하는 경우도 있다.
큐 (queue) 의 특성 순서가순서가 있는있는 동질동질 구조구조 가장 최근에 추가된 요소가 맨 뒤 (rear) 에 있고 , 가장 오래 전에 추가된 요소가 맨 앞 (front) 에 있다. FIFO(FirstFIFO(First--InIn--FirstFirst--Out)Out) 구조구조 : 요소의 추가는 큐의 끝에 이루어지고 , 요소의 제거는 앞에서 이루어진다. 연산 enqueue: 요소를 큐의 끝에 추가하는 연산 dequeue: 큐의 맨 앞에 있는 요소를 제거하고 , 그 요소를 반환해주는 연산
다. 이런 방식으로 요소가 추가되고 삭제되므로 큐를 FIFO(First-In-First-Out) 구조라 한다. 큐는 크게 enqueue, dequeue 두 가지 연산을 제공한다. enqueue는 큐에 요소를 추가할 때 사용하는 연산이고, dequeue는 큐에서 요소를 제거할 때 사용되는 연산이다. 스택과 달리 큐는 pop과 top 연산의 기능을 하나의 연산 dequeue에서 모두 제공한다.
예외 상황 enqueue: 배열을 이용하여 구현할 경우에는 더 이상 추가할 수 없는 상황이 발생할 수 있다. dequeue: 큐에 요소가 하나도 없을 수 있다.
queue =new Queue() queue.enq(2) queue.enq(3) queue.enq(1) queue.deq()
2 3 2 1 3 2 1 3
enqueue를 시도하면 그것을 처리할 수 없다. 큐에 요소가 하나도 없는 경우에 dequeue를 시도하는 경우에도 정상적으로 처리할 수 없다. 이 두 가지 경우에 해당하는 예외 클래스
QueueOverflowException과 QueueUnderflowException를 정의하여 사용한다. 이것은 스택에 서 예외 클래스를 정의한 것과 유사하게 정의하면 된다.
배열의 첫 번째 슬롯을 항상 front 로 고정해서 사용하는 방법 실제 front 멤버 변수가 필요 없음 enq 는 항상 size-1 슬롯에 추가하면 된다. rear 멤버 변수도 필요 없음 front, rear, size 중 size 하나만 사용하여 구현 가능 deq 의 경우에는 최악의 경우 n-1 개의 요소를 하나씩 왼쪽으로 이동해야 한다.
enq(10); 10 front=0,rear= enq(20); 10 20 front=0,rear= enq(30); 10 20 30 front=0,rear= deq(); 20 30
front=0, rear=-
20 30 front=0,rear=
고정된 front 설계 방법, 유동 front 설계 방법으로 나뉘어진다. 고정된 front 설계 방법에서 는 배열의 0 번째 항이 항상 큐의 맨 앞이 된다. 따라서 큐에서 요소가 제거되면 모든 요소 를 하나씩 왼쪽으로 이동해야 하는 번거로움이 있다. 하지만 큐 맨 앞의 위치가 고정되어 있으므로 맨 앞 정보를 별도로 유지할 필요가 없으며, 큐의 맨 뒤 정보도 큐에 있는 요소의 개수를 통해 알 수 있으므로 별도로 유지할 필요가 없다. 즉, 하나의 멤버변수 정보를 이용 하여 큐의 맨 앞, 큐의 맨 뒤, 큐에 있는 요소의 개수를 모두 나타낼 수 있다.
front=1,rear=
front=1, rear=
front 가 고정되어 있지 않다. enq, deq 모두 O(1) 이다. enq: rear 증가 deq: front 증가 추가적으로 front, rear 멤버 변수를 유지해야 하며 , front 와 rear 값을 통해 큐에 있는 요소의 개수를 알 수 없으면 추가적으로 size 멤버변수가 필요하다. 예에서 알 수 있듯이 full 상태와 empty 상태에서 front 와 rear 값의 차이가 동일함
enq(10); 10 front=0,rear= deq(); enq(20); 20 front=1,rear= enq(30); 20 30 front=1,rear=
front=0, rear=-
enq(40); 20 30 40 front=1,rear= enq(50); 20 30 40 50 front=1,rear= enq(60); 60 20 30 40 50
유동 front 설계 방법에서는 큐의 맨 앞 위치가 계속 바뀐다. 따라서 맨 앞 위치와 맨 뒤 위 치가 모두 변하는 형태이다. 그러므로 맨 앞 정보와 맨 뒤 정보를 별도의 멤버변수를 통해 유지해야 한다. enqueue를 할 경우에는 큐 뒤에 추가하는 것이므로 맨 뒤 정보를 나타내는 rear 값을 하나 증가시켜야 하지만 맨 앞 정보를 나타내는 front 값은 변하지 않는다. 반대
후에 그 위치에 새 요소를 복제하여 추가한다. enqueue 연산은 큐 앞 정보에는 아무런 영 향을 주지 않는다.
public Object deq() throws QueueUnderflowException{ if(isEmpty())throw new QueueUnderflowException(“Dequeue attempted on an empty queue” ); Object tmp = element[front]; elements[front] = null; front = (front + 1) % elements.length; // 불필요 size--; return tmp; // tmp.clone() 불필요 } int loc = front; front = (front + 1) % elements.length; size--; return elements[loc];
dequeue 연산은 큐 맨 앞에 있는 요소를 제거하는 연산이다. 따라서 현재 큐 앞 정보에 있 는 요소를 임시 보관한 후에 큐 front 정보를 하나 증가시키고, 임시 보관하였던 요소를 반 환하면 된다. 요소 자체를 임시 보관하는 대신에 그 위치를 보관하여 처리할 수 있지만 성 능이나 공간 활용 측면에서 두 방법은 차이가 없다.
front 와 rear 정보만을 이용하여 현재 큐의 상태 (full 또는 empty) 를 판별할 수 있는가? 현재 구현의 경우에는 두 조건이 같다. 두 조건이 같으므로 이 조건을 구분하는 boolean flag 을 사용한다. size 를 사용하는 것과 차이가 없음
배열의 한 공간을 사용하지 않으면 판별할 수 있다. (How?)
public void enq(Object item) throws QueueOverflowException{ if(isFull()) throw new QueueOverflowException(“…”); Listable x = (Listable)item; rear = (rear + 1) % elements.length;elements[rear] = x.clone(); isFull = ((rear + 1) % elements.length)==front)? true : false; }
앞서 언급한 바와 같이 유동 front 설계 방식에서는 큐 맨 앞 정보와 맨 뒤 정보만을 이용하 여 큐의 상태를 판별하기가 어렵다. 이것은 큐가 비어있는 상태와 큐가 꽉 찬 상태에서 큐 의 맨 앞 정보와 맨 뒤 정보의 상태가 같기 때문이다. 이것을 구별하기 위한 별도의 flag을 유지하는 방법을 생각할 수 있지만 이것은 크기 정보를 추가로 유지하는 것에 비해 공간적 인 이점이 없다. 그런데 배열의 한 공간을 늘 사용하지 않으면 별도의 flag을 사용하지 않고 두 상태를 판별할 수 있다.
front=5, rear=
front=5, rear=
이 방법은 size 정보를 유지하지 않아도 된다. 하지만 항상 배열 한 슬롯이 사용되지 않는다. 공간 측면에서는 기존 size 를 사용하는 것과 차이는 없다. 시간 측면에서는 size 를 유지하는 비용이 없으므로 효율적이다.
front=5, (^10) rear= 10 20 30 40 50 20 30 40 50 front=0,rear= 30 40 50 front=1,rear= front=1, (^30 40 50 60) rear=
deq(); deq(); enq(60);
전 위치를 가리키도록 하면 별도의 flag을 사용하지 않고 큐의 맨 앞 정보와 맨 뒤 정보를 이용하여 큐의 상태를 판별할 수 있다. 하지만 이 경우에도 늘 한 공간을 낭비하게 되므로 기존의 요소의 개수를 유지하는 방식에 비해 별로 향상된 점이 없다.