스택과큐

Download Report

Transcript 스택과큐

3장
stack and queue
3.1 스 택
 정의
• 선형리스트 구조의 특별한 형태로 데이타의 삽입과 삭제가
한쪽 끝(top)에서만 일어나는 구조
• push down 리스트 또는 LIFO(Last In First Out) 리스트
– 데이타의 삽입과 삭제가 한쪽 끝에서만 일어나므로 가장 나중에
삽입한 데이타가 제일 먼저 삭제
 사용분야
입력
(삽입)
a2
a1
a0
스택의 동작구조
top
출력
(삭제)
• 서브루틴의 복귀주소
(return address)를
저장할 경우
• 수식 계산
스택의 표현과 연산




프로그램 실행 시작시 ’top’이 초기치 ’-1’을 가리킴
삽입할 자료가 발생하면 위치를 하나씩 증가
삭제시 위치가 하나씩 감소
가장 나중에 삽입된 자료가 가장 먼저 삭제되는
LIFO(Last-In First-Out) 구조
 스택의 연산
• Create : 스택을 생성한다.
• Push : 스택에 ’top’이 가리키는 위치에 자료를 삽입한다.
• IsFull : top >= n-1일 경우 스택에 더 이상의 공간이 없음을
나타낸다. ( n은 스택의 크기)
• Pop : 스택의 ’top’이 가리키는 위치에 있는 자료를 삭제.
• IsEmpty : top < 0일 경우에 스택에 삭제할 자료가 없음을
나타낸다.
스택의 표현과 연산
 스택 표현
• 1차원 배열 표현 - 스택이 포함할 수 있는 최대 크기를 알 수
있을 때
• 연결 리스트로 표현 - 스택의 최대 크기를 알 수 없을 때
top
D
C
B
4
top
D
C
B
A
(a) 배열표현
(b) (a)를 연결리스트로 표현
A
(1) 스택 생성(Create)
• item 배열: 스택의 항목
• top: 현 스택의 top 위치
#define
typedef
STACK_SIZE 50
struct {
int value;
} element;
element stack[STACK_SIZE];
int top = -1; /* 초기 값 */
 스택 생성(Create)
(2) 자료 삽입(PUSH)
void push(int top, element item)
{
if(top >= (STACK_SIZE - 1)) {
printf("stack is full\n");
return;
}
stack[++top] = item;
}
(3) 자료 삭제(POP)
element
{
pop(int top)
if(top == -1) {
printf("stack is empty\n");
return;
}
else
return stack[(top)--];
}
스택 오버플로우 처리 (1/2)
 하나의 기억장소에 2개 스택을 보관 운영하는 방법
• top : 입출력이 발생되는 스택의 끝
• bottom : 스택의 시작 위치
STACK1
STACK2
좌우로 이동할 수 있음
가용 공간
STACK1
bottom1
top1
STACK2
top2
bottom2
스택 오버플로우 처리 (2/2)
 하나의 기억장소에 n개의 스택을 보관 운영하는 방법
STACK1
bottom1
초기상태
STACK2
top1
bottom2
STACK3
top2
bottom3
top3
bottom4
···
STACK4
topn-1
bottomn
topn
bottom1 = top1
bottom2 = top2

bottomn = topn
• topi = bottomi 이면 i 번째 스택의 empty
• topi = bottomi+1 이면 i 번째 스택의 포화 상태
• topi > bottomi+1 이면 i번째 스택의 오버플로
– 기억 장소의 빈 공간을 찾아 기억 공간을 재압축(repacking)
3.2 큐(Queue)
 정의
• 한쪽 끝에서 항목들이 삭제되고, 다른 한쪽 끝에서 항목들이 삽입되는
선형 리스트
• FIFO(First-In First-Out) : 가장 먼저 삽입된 항목이 가장 먼저
삭제
삭제
삽입
-1
프런트(front)
0
1
2
3
A
B
C
D
리어(rear)
 응용
• 작업 스케줄링, 대기 행렬 이론(queueing theory)
큐의 배열 표현과 연산 (1/2)
 큐의 표현
• 1차원 배열 : Queue[T]
Queue = [Q0, Q1, Q2,...QT-1]
front(Q) = Q0
rear (Q) = QT-1
– front : 실제 큐의 front보다 하나 앞을 가리킴
– rear : 실제 큐의 rear
– 큐가 빈 상태 : front = rear
– 큐의 초기조건 : front = rear = -1
큐의 배열 표현과 연산 (2/2)
 큐의 삽입과 삭제
-1
0
1
2
3
···
A
B
C
D
front
front
(삭제)
rear
···
rear
(삽입)
(1) 큐 생성
#define
typedef
QUEUE_SIZE 50
struct {
int value;
} element;
element queue[QUEUE_SIZE];
int rear = -1;
int front = -1;
(2) 자료 삽입(Add)
void add(int rear, element item)
{
if(rear == (QUEUE_SIZE - 1)) {
printf("queue is full!\n");
return;
}
queue[++rear] = item;
}
(3) 자료 삭제(Delete)
element
{
delete(int front, int rear)
if(front == rear) {
printf("queue is empty!\n");
return;
}
return queue[++front];
}
큐의 단점과 해결책
 큐의 단점
• rear가 배열의 마지막 원소를 가리키는 경우 (rear ==
QUEUE_SIZE - 1)가 반드시 QUEUE_SIZE 개의 항목이 차
있는 것을 의미하지 않음.
• front == -1, rear == QUEUE_SIZE - 1 이면 오버플로우
• front가 -1보다 크면 데이타 항목이 삭제된 것이므로 큐에 빈
공간이 남아 있음.
• 계속 항목을 삭제하면 rear pointer와 front pointer가
만나게 되고 공백 큐가 되는데도 오버플로우 현상 발생.
 해결책
• 큐 전체를 왼쪽으로 옮기고 front를 -1로 하고 rear의 값을
조정.
• 원형 큐
– 큐의 배열을 선형으로 표현하지 않고 원형으로 표현하는 방법.
3.3 원형 큐(Circular Queue) (1/3)
 원형 큐의 정의
• 큐의 배열(arrangement)을 원형으로 표현.
• 큐를 구성하는 배열의 처음과 끝을 이어놓은 형태의 큐.
• Front = 0; rear = 0;
N개의 원소를 갖는 원형 큐
…
[4]
…
[n-3]
I4
I3
[3]
[n-2]
I2
[2]
I1
[1]
[n-1]
[0]
front = 0; rear = 4
[n-4]
I1
[4]
I2
I3
[3]
I5
[2]
[1]
[n-3]
[n-2]
I4
[n-1]
[0]
front = n-5; rear = 0
3.3 원형 큐(Circular Queue) (2/3)
(1) 자료 삽입(CQqadd)
void CQadd(int front, int rear, element item)
{
rear = (rear + 1) % QUEUE_SIZE;
if(front == rear) {
printf("queue is full!\n");
return;
}
queue[rear] = item;
}
3.3 원형 큐(Circular Queue) (3/3)
(2) 자료 삭제(CQdelete)
element
{
CQdelete(int front, int rear)
if(front == rear) {
printf("queue is empty!\n");
return;
}
else {
front = (front + 1) % QUEUE_SIZE;
return queue[front];
}
}
3.4 데크(Deque : Double Ended Queue)
 정의
• 삽입과 삭제가 양쪽 끝에서 모두 허용될 수 있는 선형 리스트
• 스택과 큐를 선형리스트 구조에 복합 시킨 형태.
• 두개의 스택의 bottom 부분이 서로 연결된 것.
삭제
삽입
A
end1
B
C
D
end2
삽입
삭제
데크의 표현 및 연산
 스택을 이용하는 경우
• 두개의 스택을 연결.
• 스택들이 뚜렷한 경계가 있거나, 스택의 마지막 원소의 위치를
저장해 놓을 수 있다면 스택들이 같은 기억장소 공유 가능.
• 기억 장소 오버플로우 발생.
• 양쪽 끝에서 삽입/삭제 가능하기 때문에 두개의 포인터(left,
right 또는 top, bottom) 필요.
 연결 리스트를 이용하는 경우
• 한 데이타 항목을 첨가해야 할 때 사용 가능한 기억 공간을
확보해 주는 함수 이용.
• 원소 삭제될 때에는 기억 장소 관리 함수를 이용하여 반환
Deque의 종류
 입력 제한 데크
• 입력이 한쪽 끝에서만 일어나도록 제한 (스크롤(scroll))
 출력 제한 데크
• 출력이 한쪽 끝에서만 일어나도록 제한 (셀프(shelf))
3.5 수식표현 (1/4)
 연산자의 우선순위
• 연산에서 괄호가 없을 경우 또는 괄호 내에서 우선순위가 필요
연산자
우선 순위
단항연산자 -, !
6
*, /, %
5
+, -
4
<, <=, >, >=
3
&&
2
∥
1
수식표현 (2/4)
 수식 표기법
• 중위(infix) 표기 방법
– <피연산자-연산자-피연산자> 예) A + B
• 전위(prefix) 표기 방법
– <연산자-피연산자-피연산자> 예) + A B
– 중위 표기 -> 전위 표기 방법으로의 전환
예) (((((-A) / B) * C) + (D * E)) - (A * C))
=> - + * / - A B C * D E * A C
• 후위(postfix or reverse polish) 표기 방법
– <피연산자-피연산자-연산자> 예) A B +
– 컴퓨터가 수식을 계산하기에 가장 적합한 방법
– 컴파일러 : 중위표기 수식을 후기 표기 방법으로 변환한 후 스택을 이용 수식
계산
수식표현 (3/4)
 후위 표기식의 장점
• 괄호를 필요로 하지 않는다.
• 연산자들의 우선 순위가 필요 없게 된다.
• 스택을 사용하면 식을 왼쪽에서 오른쪽으로 읽어 가면서 쉽게
계산할 수 있다.
 후위 표기식 계산 알고리즘
• 수식을 구성하는 원소(token)을 읽어서 그것이 피연산자이면
스택에 넣고, 연산자이면 필요한 수만큼의 피연산자를 꺼내어
연산을 수행하고 그 결과를 스택에 다시 넣는다
수식표현 (4/4)
 스택을 이용한 후위 표기방식의 계산: 2 3 + 4 *
①’2’ 와 ‘3’을 스택에 삽입(push) 한다.
②’+’ 연산자를 만나면 스택에 있는 자료들을 꺼내어(pop) ’+’연산을
수행(5).
③’5’를 스택에 삽입(push) 한다.
④’4’를 스택에 삽입(push) 한다.
⑤’*’연산자를 만나면 스택에 있는 자료들을 꺼내어(pop) ’*’연산을
수행한다(20).
⑥’20’을 스택에 삽입(push) 한다.
⑦ 더 이상의 입력이 없으므로 스택의’top’에 있는’20’을 결과로
한다.
수식표현 (4/4)
 중위 표기식을 후위 표기식으로의 변환 알고리즘
• 중위 표기실을 연산자들의 우선순위에 따라 완전한 괄호를 포함하는
식으로 표현
• 모든 연산자들을 그와 대응하는 오른쪽 괄호의 밖으로 옮김
• 괄호를 모두 제거
수식표현 (4/4)
 중위에서 후위 표기식으로 변환 시 스택 이용
① 피연산자는 즉시
출력한다.
② 스택의’top’에 있는
연산자의 우선
순위(precedence
hierarchy)가 스택에
들어올 연산자보다
작으면’push’한다.
• 예) x + y * z
수식표현 (4/4)
 괄호가 있는 경우의 변환
① 피연산자는 출력한다.
② 오른쪽 괄호가 나타날 때까지 연산자는’push’ 한다.
③ 오른쪽 괄호가 나타나면 왼쪽 괄호까지 연산자는 ‘pop’ 하고 왼쪽
괄호는 삭제한다.
④ 새 연산자가 스택의’top’ 에 있는 연산자보다 우선 순위가 높으면
‘push’하고, 아니면 ‘pop’ 한다.
수식표현 (4/4)
예) a * (b + c) * d