제 1 장 기본 개념

Download Report

Transcript 제 1 장 기본 개념

자료 구조
제 3 장 : 스택과 큐
이형원
강릉대학교 컴퓨터공학과
학습 내용
스택 ADT
큐 ADT
스택의 사용 예제
 미로 문제
 수식 계산
다중 스택과 큐
2
스택(stack)
한쪽 끝(top)에서만 삽입/삭제가 되는 순서 리
스트
Last-In-First-Out(LIFO)
A
top
B
A
top
C
B
A
top
D
C
B
A
top
E
D
C
B
A
top
D
C
B
A
top
사용 예 : 시스템 스택
3
스택 ADT: structure Stack
objects: 0개 이상의 원소를 가진 유한 순서 리스트
functions: stack∈Stack, item∈element, max_stack_size∈양정수
Stack CreateS(max_stack_size) ::=
최대 크기 max_stack_size인 공백 스택 생성
Boolean IsFull(stack, max_stack_size) ::=
if (stack의 원소수 == max_stack_size) return TRUE
else return FALSE
Stack Add(stack, item) ::= if (IsFull(stack)) stack_full
else stack top에 item을 삽입하고
return
Boolean IsEmpty(stack) ::=
if (stack == CreateS(max_stack_size)) return TRUE
else return FALSE
Element Delete(stack) ::= if (IsEmpty(stack)) return
4
else stack top의 item을 제거해서
스택의 구현
Stack CreateS(max_stack_size) ::=
#define MAX_STACK_SIZE 100 /*최대스택크기*/
typedef struct { int key;
/* 다른 필드 */
} element;
element stack[MAX_STACK_SIZE];
int top = -1;
Boolean IsEmpty(Stack) ::= top < 0;
Boolean IsFull(Stack) ::= top >= MAX_STACK_SIZE-1;
void add(int *top, element item) {
if (*top >= MAX_STACK _SIZE-1) {
stack_full();
return;
}
stack[++*top] = item;
}
element delete(int *top) {
if (*top == -1)
return stack_empty();
return stack[(*top)--];}
}
5
큐(queue)
한쪽 끝(rear)에서 삽입, 반대쪽 끝(front)에서
삭제가 일어나는 순서 리스트
First-In-First-Out(FIFO)
A
rear
front
B
A
rear
front
C
B
A
rear
front
D
C
B
A
rear
D
C
B
front
rear
front
6
큐 ADT: structure Queue
objects: 0개 이상의 원소를 가진 유한 순서 리스트
functions: queue∈Queue, item∈element, max_queue_size∈양
정수
Queue CreateQ(max_queue_size) ::=
최대 크기가 max_queue_size인 공백 큐를 생성
Boolean IsFullQ(queue, max_queue_size ) ::=
if (queue의 원소수 == max_queue_size) return TRUE
else return FALSE
Queue AddQ(queue,item) ::= if (IsFull(queue)) queue_full
else queue 뒤에 item 삽입하고
반환
Boolean IsEmptyQ(queue) ::=
if (queue == CreateQ(max_queue_size)) return TRUE
else return FALSE
Element DeleteQ(queue) ::= if (IsEmpty(queue)) return 7
큐의 구현: 순차 배열
Queue CreateQ(max_queue_size) ::=
#define MAX_QUEUE_SIZE 100 /* 큐의 최대크기 */
typedef struct { int key;
/* 다른 필드 */
} element;
element queue[MAX_QUEUE_SIZE];
int rear = -1;
int front = -1;
Boolean IsEmptyQ(queue) ::= front == rear
Boolean IsFullQ(queue) ::= rear == MAX_QUEUE_SIZE-1
void addq(int *rear, element item) {
{
if (*rear == MAX_QUEUE_SIZE-1) {
queue_full();
return;
}
queue[++*rear] = item;
}
element deleteq(int *front, int rea
if (*front == rear)
return queue_empty();
return queue[++*front];
}
8
큐의 구현: 순차 배열(계속)
단점: rear = MAX_QUEUE_SIZE - 1이 되는 경우
 전체 큐를 왼쪽으로 이동, front=-1, rear 조정
Q. 시간 복잡도 ?
 예제 [작업 스케쥴링] : O.S.에 의한 작업 큐의 생성
front
-1
-1
-1
-1
0
1
rear
-1
0
1
2
2
2
Q[0] Q[1] Q[2] Q[3]
J1
J1
J1
J2
J2
J2
J3
J3
J3
설명
공백큐
Job 1의 삽입
Job 2의 삽입
Job 3의 삽입
Job 1의 삭제
Job 2의 삭제
9
큐의 구현: 원형 배열
초기화: front = rear = 0
공백 큐: front = rear
포화 큐: 원소 수 =
MAX_QUEUE_SIZE-1
공백 큐
[2]
[3]
[2]
[3]
J2
[1]
[4]
[0]
[1]
[5]
J1
[4]
[0]
front = 0
rear = 0
Q. Why ?
[1]
포화 큐
[3]
J2
[3]
J8
J4
J5
[0]
[2]
J3
J1
[5]
front = 0
rear = 3
포화 큐
[2]
J3
[4]
[1]
J7
[4]
J6
[5]
front = 0
rear = 5
J9
[0]
J5
[5]
front = 4
rear = 3
10
큐의 구현: 원형 배열(계속)
void addq(int *front, int *rear, element item) {
*rear = (*rear+1) % MAX_QUEUE_SIZE;
if (front == *rear) {
queue_full(rear); /* rear를 리세트시키고 에러를 프린트
*/
return;
}
queue[*rear] = item;
}
void deleteq(int *front, int rear) {
element item;
/* queue의 front 원소를 삭제하여 그것을 item에 놓음 */
if (*front == rear) return queue_empty();
*front = (*front+1) % MAX_QUEUE_SIZE;
11
return queue[*front];
미로 문제 : 자료 구조
1) m x p 미로: 2차원 배열 maze[m+2][p+2]



maze[row][col] = 0(path) or 1(no path)
가장 자리는 모두 1
입구 maze[1][1], 출구 maze[m][p]
2) 이동 방향: 미리 정의된 이동 테이블 move[8]

구조
typedef struct {
short int vert;
short int horiz;
} offsets;
offsets move[8];
12
미로 문제 : 자료 구조(계속)
 move[8]의 내용
name
N
NE
E
SE
S
SW
W
NW
dir
0
1
2
3
4
5
6
7
move[dir].vert
-1
-1
0
1
1
1
0
-1
move[dir].horiz
0
NW
1
[i-1][j-1]
1
1
W
[i][j-1]
0
-1
[i+1][j-1]
-1
SW
-1
 현 위치 maze[row][col] 이동 위치
maze[next_row][next_col]
next_row = row + move[dir].vert
next_col = col + move[dir].horiz
N
[i-1][j]
X
NE
[i-1][j+1]
[i][j+1]
[i] [j]
[i+1][j]
S
[i+1][j+1]
SE
13
E
미로 문제 : 자료 구조(계속)
3) 시도한 경로의 재시도 방지: mark[m+2][p+2]


mark[row][col] = 1 /* 한번 방문한 경우 (지나간 경로)*/
경로의 탐색
현위치 저장 후 방향 선택 : N에서 시계방향
잘못된 경로의 선택 시는 backtracking후 다른 방향 시도
4) 시도한 경로의 유지: 스택 element
stack[MAX_SIZE];
#define MAX_SIZE 100 /* 스택의 최대 크기 */
typedef struct {
Q. 스택 크기의 최대
값?
short int row;
short int col;
14
short int dir;
미로 문제 : 초기 미로 알고리즘
initiallize a stack to the maze's entrance coordinates and direction to
north;
while (stack is not empty) {
/* 스택의 톱에 있는 위치로 이동*/
<rol, col, dir> = delete from top of stack;
while (there are more moves from current position) {
<next_row, next_col> = coordinate of next move;
dir = direction of move;
if ((next_row == EXIT_ROW) && (exit_col == EXIT_COL)) success;
if (maze[next_row][next_col] == 0) && mark[next_row][next_col] ==
0){
/* 가능하지만 아직 이동해보지 않은 이동 방향 */
mark[next_row][next_col] = 1;
/* 현재의 위치와 방향을 저장 */
add <row, col, dir> to the top of the stack;
row = next_row;
col = next_col;
dir = north;
}
15
}
}
미로 문제 : 미로 탐색 함수
void path(void) {
int i, row, col, next_row, next_col, dir, found=FALSE;
element position; mark[1][1]=1; top=0;
stack[0].row=1; stack[0].col=1; stack[0].dir=1;
while (top>-1 && !found) {
position=delete(&top); row=position.row; col=position.col;
dir=position.dir;
while (dir<8 && !found) {
next_row = row + move[dir].vert; /* dir 방향으로 이동 */
next_col = col + move[dir].horiz;
if (next_row==EXIT_ROW && next_col==EXIT_COL) found = TRUE;
else if (!maze[next_row][next_col] && !mark[next_row][next_col]) {
mark[next_row][next_col]) = 1;
position.row = row; position.col = col; position.dir = ++dir;
add(&top, position);
row = next.row; col = next.col; dir = 0;
}
else ++dir;
}
}
16
……
수식의 계산
C의 연산자 우선 순위 계층
토큰
우선순위
--, ++
16
&, *, unary - +
15
*, /, %
13
+, 12
>, >=, <, <=
10
==, !=
9
&&
5
LR
||
4
결합성
LR
RL
LR
LR
LR
LR
LR
17
수식의 계산(계속)
수식의 표현
중위 (infix) 표기
a*b/c
(1 + 2) * 7
후위 (postfix) 표기
ab*c/
12+7*
전위 (prefix) 표기
/*abc
*+127
후위 표기식의 장점 : 컴파일러(기계)에게 편리
 괄호 및 연산자간의 우선 순위가 불필요
 한 번만 수식을 scan하면서 스택을 이용하여 쉽게 계산
 중위 표기식을 후위 표기식으로 변환 후 계산
18
후위 표기식의 계산
algorithm (예 : 3.14)
L-->R로 scan
피연산자 : 스택에 삽입
연산자 : 필요한 피연산자 수만큼 스택에서 가져
옴
연산 수행 후 결과를 스택에 저장
식의 끝 : 스택의 top에서 해답을 가져옴
19
후위 표기식 처리 함수
typedef enum {lparen,rparen,plus,minus,times,divide,mod,eos,operand}
precedence;
int stack[MAX_STACK_SIZE]; /* 전역 배열 */
char expr[MAX_EXPR_SIZE]; /* 입력 스트링 */
int eval(void) {
precedence token; char symbol; int op1,op2, top=-1, n=0;
token = get_token(&symbol, &n);
while (token != eos) {
if (token == operand) add(&top, symbol-'0'); / *스택 삽입 */
else {
op2 = delete(&top);
op1 = delete(&top);
switch(token) {
case plus : add(&top, op1+op2);
break;
case minus : add(&top, op1-op2); break;
……
case mod : add(&top, op1%op2);
}
}
token = get_token(&symbol, &n);
20
}
return delete(&top); /* 결과를 반환 */
후위 표기식 처리 함수(계속)
precedence get_token (char *symbol, int *n) {
/* 다음 토큰을 취한다. symbol은 문자 표현이며, token은 그것의 열거된
값으로 표현되고, 명칭으로 반환된다. */
*symbol = expr[(*n)++];
switch (*symbol) {
case '(' : return lparen;
case ')' : return rparen;
case '+' : return plus;
case '-' : return minus;
case '/' : return divide;
case '*' : return times;
case '%' : return mod;
case ' ' : return eos;
default : return operand;
}
}
21
중위 표기 후위 표기
수작업에 의한 중위->후위 변환
① 식을 괄호로 묶음
② 연산자를 오른쪽 괄호와 대치
③ 괄호 삭제
 예. 전위 표기 : a / b - c + d * e - a * c
((((a / b) - c) + (d * e)) - (a * c))
((((a b) / c) - (d e) *) + (a c) *) 후위 표기 : a b / c - d e * + a c * ==> 컴퓨터 처리로는 비효율적 (∵ 2 pass가 필요 )
 How to infix to postfix in one pass ?
• 피연산자들의 순서: 중위/후위 동일  scan하면서 바로 출력 가능
• 연산자 순서: 높은 우선 순위가 먼저 출력  스택을 이용
22
중위 표기 후위 표기(계속)
중위  후위 변환의 핵심
1. token = 피연산자: 출력
2. token = ‘)’: (가 나올 때까지 스택의 연산자 출력
3. 우선순위(top) ≥ 우선순위(token): 스택의 연산자 출력
4. 우선순위(top) < 우선순위(token): 스택에 token 삽입
주의. ‘)’: 스택에 넣지 않음
‘(‘ : 스택 밖에서는 가장 높은 우선 순위
but 스택 안에서는 가장 낮은 우선 순위
Q. Why ?
23
중위 표기 후위 표기(계속)
중위 표기식 a*(b+c)*d의 후위 표기식으로의 변환
토큰
a
*
(
b
+
c
)
*
d
eos
스택
[0] [1]
*
*
*
*
*
*
*
*
(
(
(
(
Output
[2]
+
+
a
a
a
ab
ab
abc
abc+
abc+*
abc+*d
abc+*d*
24
중위 표기 후위 표기(계속)
precedence stack[MAX_STACK_SIZE];
static int isp[] = {0, 19, 12, 12, 13, 13, 13, 0}; /* 연산자 ( ) + - * / % eos의 우선순위 값
*/
static int icp[] = {20, 19, 12, 12, 13, 13, 13, 0};
void postfix(void) {
char symbol; precedence token; int n = 0; int top = 0; stack[0] = eos;
for (token==get_token(&symbol,&n); token!=eos; token==get_token(&symbol,&n))
{
if (token == operand) printf("%c", symbol);
else if (token == rparen) {
while (stack[top] != lparen) print_token(delete(&top));
delete(&top); /* 좌괄호를 버린다 */
}
else {
/* symbol의 isp  token의 icp : symbol을 제거하고 출력 */
while (isp[stack[top]] >= icp[token]) print_token(delete(&top));
add(&top, token);
}
}
25
while ((token=delete(&top)) != eos) print_token(token);
다중 스택
2 스택 : 각 스택의 최하단 원소를 양끝으로
Top[0]
Top[1]
n (>2) 스택
 이용 가능한 기억장소를 n개의 세그먼트로 분할
• 예상 크기를 아는 경우 : 크기에 따라 분할
• 예상 크기를 모르는 경우 : 똑같이 분할
– Memory[m]에서 n개 스택의 초기 구성
0
m/n
1
….
Boundary[0]
Top[0]
2 m/n
….
Boundary[1]
Top[1]
m-1
….
Boundary[2]
Top[2]
Boundary[n]
26
다중 스택 함수
#define MEMORY_SIZE 100 /* memory 크기 */
#define MAX_STACKS 10 /* 가능한 스택의 최대수 + 1 */
element memory[MEMORY_SIZE];
int top[MAX_STACKS];
int boundary[MAX_STACKS];
void create(int n) { /* n :사용자가 입력한 스택의 수 */
top[0] = boundary[0] = -1;
for (i = 1; i < n; i++) top[i] = boundary[i] = (MEMORY_SIZE/n) * i;
boundary[n] = MEMORY_SIZE -1;
}
void add(int i, element item) { /* item을 i번째 스택에 삽입 */
if (top[i] == boundary[i+1]) stack_full(i); Q. How to implement ?
memory[++top[i]] = item;
}
element delete(int i) { /* i번째 스택에서 top 원소를 제거 */
if (top[i] == boundary[i]) return stack_empty(i);
return memory[top[i]--];
}
27